From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- sp/src/game/server/AI_ResponseSystem.cpp | 6808 +++++++++++++++--------------- 1 file changed, 3404 insertions(+), 3404 deletions(-) (limited to 'sp/src/game/server/AI_ResponseSystem.cpp') diff --git a/sp/src/game/server/AI_ResponseSystem.cpp b/sp/src/game/server/AI_ResponseSystem.cpp index aa32f49d..510a1d4e 100644 --- a/sp/src/game/server/AI_ResponseSystem.cpp +++ b/sp/src/game/server/AI_ResponseSystem.cpp @@ -1,3404 +1,3404 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=============================================================================// - - -#include "cbase.h" -#include "SoundEmitterSystem/isoundemittersystembase.h" -#include "AI_ResponseSystem.h" -#include "igamesystem.h" -#include "AI_Criteria.h" -#include -#include "filesystem.h" -#include "utldict.h" -#include "ai_speech.h" -#include "tier0/icommandline.h" -#include -#include "sceneentity.h" -#include "isaverestore.h" -#include "utlbuffer.h" -#include "stringpool.h" -#include "fmtstr.h" -#include "multiplay_gamerules.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -ConVar rr_debugresponses( "rr_debugresponses", "0", FCVAR_NONE, "Show verbose matching output (1 for simple, 2 for rule scoring). If set to 3, it will only show response success/failure for npc_selected NPCs." ); -ConVar rr_debugrule( "rr_debugrule", "", FCVAR_NONE, "If set to the name of the rule, that rule's score will be shown whenever a concept is passed into the response rules system."); -ConVar rr_dumpresponses( "rr_dumpresponses", "0", FCVAR_NONE, "Dump all response_rules.txt and rules (requires restart)" ); - -static CUtlSymbolTable g_RS; - -inline static char *CopyString( const char *in ) -{ - if ( !in ) - return NULL; - - int len = Q_strlen( in ); - char *out = new char[ len + 1 ]; - Q_memcpy( out, in, len ); - out[ len ] = 0; - return out; -} - -#pragma pack(1) -class Matcher -{ -public: - Matcher() - { - valid = false; - isnumeric = false; - notequal = false; - usemin = false; - minequals = false; - usemax = false; - maxequals = false; - maxval = 0.0f; - minval = 0.0f; - - token = UTL_INVAL_SYMBOL; - rawtoken = UTL_INVAL_SYMBOL; - } - - void Describe( void ) - { - if ( !valid ) - { - DevMsg( " invalid!\n" ); - return; - } - char sz[ 128 ]; - - sz[ 0] = 0; - int minmaxcount = 0; - if ( usemin ) - { - Q_snprintf( sz, sizeof( sz ), ">%s%.3f", minequals ? "=" : "", minval ); - minmaxcount++; - } - if ( usemax ) - { - char sz2[ 128 ]; - Q_snprintf( sz2, sizeof( sz2 ), "<%s%.3f", maxequals ? "=" : "", maxval ); - - if ( minmaxcount > 0 ) - { - Q_strncat( sz, " and ", sizeof( sz ), COPY_ALL_CHARACTERS ); - } - Q_strncat( sz, sz2, sizeof( sz ), COPY_ALL_CHARACTERS ); - minmaxcount++; - } - - if ( minmaxcount >= 1 ) - { - DevMsg( " matcher: %s\n", sz ); - return; - } - - if ( notequal ) - { - DevMsg( " matcher: !=%s\n", GetToken() ); - return; - } - - DevMsg( " matcher: ==%s\n", GetToken() ); - } - - float maxval; - float minval; - - bool valid : 1; //1 - bool isnumeric : 1; //2 - bool notequal : 1; //3 - bool usemin : 1; //4 - bool minequals : 1; //5 - bool usemax : 1; //6 - bool maxequals : 1; //7 - - void SetToken( char const *s ) - { - token = g_RS.AddString( s ); - } - - char const *GetToken() - { - if ( token.IsValid() ) - { - return g_RS.String( token ); - } - return ""; - } - void SetRaw( char const *raw ) - { - rawtoken = g_RS.AddString( raw ); - } - char const *GetRaw() - { - if ( rawtoken.IsValid() ) - { - return g_RS.String( rawtoken ); - } - return ""; - } - -private: - CUtlSymbol token; - CUtlSymbol rawtoken; -}; - -struct Response -{ - DECLARE_SIMPLE_DATADESC(); - - Response() - { - type = RESPONSE_NONE; - value = NULL; - weight.SetFloat( 1.0f ); - depletioncount = 0; - first = false; - last = false; - } - - Response( const Response& src ) - { - weight = src.weight; - type = src.type; - value = CopyString( src.value ); - depletioncount = src.depletioncount; - first = src.first; - last = src.last; - } - - Response& operator =( const Response& src ) - { - if ( this == &src ) - return *this; - weight = src.weight; - type = src.type; - value = CopyString( src.value ); - depletioncount = src.depletioncount; - first = src.first; - last = src.last; - return *this; - } - - ~Response() - { - delete[] value; - } - - ResponseType_t GetType() { return (ResponseType_t)type; } - - char *value; // fixed up value spot // 4 - float16 weight; // 6 - - byte depletioncount; // 7 - byte type : 6; // 8 - byte first : 1; // - byte last : 1; // -}; - -struct ResponseGroup -{ - DECLARE_SIMPLE_DATADESC(); - - ResponseGroup() - { - // By default visit all nodes before repeating - m_bSequential = false; - m_bNoRepeat = false; - m_bEnabled = true; - m_nCurrentIndex = 0; - m_bDepleteBeforeRepeat = true; - m_nDepletionCount = 1; - m_bHasFirst = false; - m_bHasLast = false; - } - - ResponseGroup( const ResponseGroup& src ) - { - int c = src.group.Count(); - for ( int i = 0; i < c; i++ ) - { - group.AddToTail( src.group[ i ] ); - } - - rp = src.rp; - m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat; - m_nDepletionCount = src.m_nDepletionCount; - m_bHasFirst = src.m_bHasFirst; - m_bHasLast = src.m_bHasLast; - m_bSequential = src.m_bSequential; - m_bNoRepeat = src.m_bNoRepeat; - m_bEnabled = src.m_bEnabled; - m_nCurrentIndex = src.m_nCurrentIndex; - } - - ResponseGroup& operator=( const ResponseGroup& src ) - { - if ( this == &src ) - return *this; - int c = src.group.Count(); - for ( int i = 0; i < c; i++ ) - { - group.AddToTail( src.group[ i ] ); - } - - rp = src.rp; - m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat; - m_nDepletionCount = src.m_nDepletionCount; - m_bHasFirst = src.m_bHasFirst; - m_bHasLast = src.m_bHasLast; - m_bSequential = src.m_bSequential; - m_bNoRepeat = src.m_bNoRepeat; - m_bEnabled = src.m_bEnabled; - m_nCurrentIndex = src.m_nCurrentIndex; - return *this; - } - - bool HasUndepletedChoices() const - { - if ( !m_bDepleteBeforeRepeat ) - return true; - - int c = group.Count(); - for ( int i = 0; i < c; i++ ) - { - if ( group[ i ].depletioncount != m_nDepletionCount ) - return true; - } - - return false; - } - - void MarkResponseUsed( int idx ) - { - if ( !m_bDepleteBeforeRepeat ) - return; - - if ( idx < 0 || idx >= group.Count() ) - { - Assert( 0 ); - return; - } - - group[ idx ].depletioncount = m_nDepletionCount; - } - - void ResetDepletionCount() - { - if ( !m_bDepleteBeforeRepeat ) - return; - ++m_nDepletionCount; - } - - void Reset() - { - ResetDepletionCount(); - SetEnabled( true ); - SetCurrentIndex( 0 ); - m_nDepletionCount = 1; - - for ( int i = 0; i < group.Count(); ++i ) - { - group[ i ].depletioncount = 0; - } - } - - bool HasUndepletedFirst( int& index ) - { - index = -1; - - if ( !m_bDepleteBeforeRepeat ) - return false; - - int c = group.Count(); - for ( int i = 0; i < c; i++ ) - { - Response *r = &group[ i ]; - - if ( ( r->depletioncount != m_nDepletionCount ) && r->first ) - { - index = i; - return true; - } - } - - return false; - } - - bool HasUndepletedLast( int& index ) - { - index = -1; - - if ( !m_bDepleteBeforeRepeat ) - return false; - - int c = group.Count(); - for ( int i = 0; i < c; i++ ) - { - Response *r = &group[ i ]; - - if ( ( r->depletioncount != m_nDepletionCount ) && r->last ) - { - index = i; - return true; - } - } - - return false; - } - - bool ShouldCheckRepeats() const { return m_bDepleteBeforeRepeat; } - int GetDepletionCount() const { return m_nDepletionCount; } - - bool IsSequential() const { return m_bSequential; } - void SetSequential( bool seq ) { m_bSequential = seq; } - - bool IsNoRepeat() const { return m_bNoRepeat; } - void SetNoRepeat( bool norepeat ) { m_bNoRepeat = norepeat; } - - bool IsEnabled() const { return m_bEnabled; } - void SetEnabled( bool enabled ) { m_bEnabled = enabled; } - - int GetCurrentIndex() const { return m_nCurrentIndex; } - void SetCurrentIndex( byte idx ) { m_nCurrentIndex = idx; } - - CUtlVector< Response > group; - - AI_ResponseParams rp; - - bool m_bEnabled; - - byte m_nCurrentIndex; - // Invalidation counter - byte m_nDepletionCount; - - // Use all slots before repeating any - bool m_bDepleteBeforeRepeat : 1; - bool m_bHasFirst : 1; - bool m_bHasLast : 1; - bool m_bSequential : 1; - bool m_bNoRepeat : 1; - -}; - -struct Criteria -{ - Criteria() - { - name = NULL; - value = NULL; - weight.SetFloat( 1.0f ); - required = false; - } - Criteria& operator =(const Criteria& src ) - { - if ( this == &src ) - return *this; - - name = CopyString( src.name ); - value = CopyString( src.value ); - weight = src.weight; - required = src.required; - - matcher = src.matcher; - - int c = src.subcriteria.Count(); - for ( int i = 0; i < c; i++ ) - { - subcriteria.AddToTail( src.subcriteria[ i ] ); - } - - return *this; - } - Criteria(const Criteria& src ) - { - name = CopyString( src.name ); - value = CopyString( src.value ); - weight = src.weight; - required = src.required; - - matcher = src.matcher; - - int c = src.subcriteria.Count(); - for ( int i = 0; i < c; i++ ) - { - subcriteria.AddToTail( src.subcriteria[ i ] ); - } - } - ~Criteria() - { - delete[] name; - delete[] value; - } - - bool IsSubCriteriaType() const - { - return ( subcriteria.Count() > 0 ) ? true : false; - } - - char *name; - char *value; - float16 weight; - bool required; - - Matcher matcher; - - // Indices into sub criteria - CUtlVector< unsigned short > subcriteria; -}; - -struct Rule -{ - Rule() - { - m_bMatchOnce = false; - m_bEnabled = true; - m_szContext = NULL; - m_bApplyContextToWorld = false; - } - - Rule& operator =( const Rule& src ) - { - if ( this == &src ) - return *this; - - int i; - int c; - - c = src.m_Criteria.Count(); - for ( i = 0; i < c; i++ ) - { - m_Criteria.AddToTail( src.m_Criteria[ i ] ); - } - - c = src.m_Responses.Count(); - for ( i = 0; i < c; i++ ) - { - m_Responses.AddToTail( src.m_Responses[ i ] ); - } - - SetContext( src.m_szContext ); - m_bMatchOnce = src.m_bMatchOnce; - m_bEnabled = src.m_bEnabled; - m_bApplyContextToWorld = src.m_bApplyContextToWorld; - return *this; - } - - Rule( const Rule& src ) - { - int i; - int c; - - c = src.m_Criteria.Count(); - for ( i = 0; i < c; i++ ) - { - m_Criteria.AddToTail( src.m_Criteria[ i ] ); - } - - c = src.m_Responses.Count(); - for ( i = 0; i < c; i++ ) - { - m_Responses.AddToTail( src.m_Responses[ i ] ); - } - - SetContext( src.m_szContext ); - m_bMatchOnce = src.m_bMatchOnce; - m_bEnabled = src.m_bEnabled; - m_bApplyContextToWorld = src.m_bApplyContextToWorld; - } - - ~Rule() - { - delete[] m_szContext; - } - - void SetContext( const char *context ) - { - delete[] m_szContext; - m_szContext = CopyString( context ); - } - - const char *GetContext( void ) const { return m_szContext; } - - bool IsEnabled() const { return m_bEnabled; } - void Disable() { m_bEnabled = false; } - bool IsMatchOnce() const { return m_bMatchOnce; } - bool IsApplyContextToWorld() const { return m_bApplyContextToWorld; } - - // Indices into underlying criteria and response dictionaries - CUtlVector< unsigned short > m_Criteria; - CUtlVector< unsigned short> m_Responses; - - char *m_szContext; - bool m_bApplyContextToWorld : 1; - - bool m_bMatchOnce : 1; - bool m_bEnabled : 1; -}; -#pragma pack() - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -abstract_class CResponseSystem : public IResponseSystem -{ -public: - CResponseSystem(); - ~CResponseSystem(); - - // IResponseSystem - virtual bool FindBestResponse( const AI_CriteriaSet& set, AI_Response& response, IResponseFilter *pFilter = NULL ); - virtual void GetAllResponses( CUtlVector *pResponses ); - - virtual void Release() = 0; - - virtual void DumpRules(); - - virtual void Precache(); - - virtual void PrecacheResponses( bool bEnable ) - { - m_bPrecache = bEnable; - } - - bool ShouldPrecache() { return m_bPrecache; } - bool IsCustomManagable() { return m_bCustomManagable; } - - void Clear(); - - void DumpDictionary( const char *pszName ); - -protected: - - virtual const char *GetScriptFile( void ) = 0; - void LoadRuleSet( const char *setname ); - - void ResetResponseGroups(); - - float LookForCriteria( const AI_CriteriaSet &criteriaSet, int iCriteria ); - float RecursiveLookForCriteria( const AI_CriteriaSet &criteriaSet, Criteria *pParent ); - -public: - - void CopyRuleFrom( Rule *pSrcRule, int iRule, CResponseSystem *pCustomSystem ); - void CopyCriteriaFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ); - void CopyResponsesFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ); - void CopyEnumerationsFrom( CResponseSystem *pCustomSystem ); - -//private: - - struct Enumeration - { - float value; - }; - - struct ResponseSearchResult - { - ResponseSearchResult() - { - group = NULL; - action = NULL; - } - - ResponseGroup *group; - Response *action; - }; - - inline bool ParseToken( void ) - { - if ( m_bUnget ) - { - m_bUnget = false; - return true; - } - if ( m_ScriptStack.Count() <= 0 ) - { - Assert( 0 ); - return false; - } - - m_ScriptStack[ 0 ].currenttoken = engine->ParseFile( m_ScriptStack[ 0 ].currenttoken, token, sizeof( token ) ); - m_ScriptStack[ 0 ].tokencount++; - return m_ScriptStack[ 0 ].currenttoken != NULL ? true : false; - } - - inline void Unget() - { - m_bUnget = true; - } - - inline bool TokenWaiting( void ) - { - if ( m_ScriptStack.Count() <= 0 ) - { - Assert( 0 ); - return false; - } - - const char *p = m_ScriptStack[ 0 ].currenttoken; - - if ( !p ) - { - Error( "AI_ResponseSystem: Unxpected TokenWaiting() with NULL buffer in %p", m_ScriptStack[ 0 ].name ); - return false; - } - - - while ( *p && *p!='\n') - { - // Special handler for // comment blocks - if ( *p == '/' && *(p+1) == '/' ) - return false; - - if ( !isspace( *p ) || isalnum( *p ) ) - return true; - - p++; - } - - return false; - } - - void ParseOneResponse( const char *responseGroupName, ResponseGroup& group ); - - void ParseInclude( CStringPool &includedFiles ); - void ParseResponse( void ); - void ParseCriterion( void ); - void ParseRule( void ); - void ParseEnumeration( void ); - - int ParseOneCriterion( const char *criterionName ); - - bool Compare( const char *setValue, Criteria *c, bool verbose = false ); - bool CompareUsingMatcher( const char *setValue, Matcher& m, bool verbose = false ); - void ComputeMatcher( Criteria *c, Matcher& matcher ); - void ResolveToken( Matcher& matcher, char *token, size_t bufsize, char const *rawtoken ); - float LookupEnumeration( const char *name, bool& found ); - - int FindBestMatchingRule( const AI_CriteriaSet& set, bool verbose ); - - float ScoreCriteriaAgainstRule( const AI_CriteriaSet& set, int irule, bool verbose = false ); - float RecursiveScoreSubcriteriaAgainstRule( const AI_CriteriaSet& set, Criteria *parent, bool& exclude, bool verbose /*=false*/ ); - float ScoreCriteriaAgainstRuleCriteria( const AI_CriteriaSet& set, int icriterion, bool& exclude, bool verbose = false ); - bool GetBestResponse( ResponseSearchResult& result, Rule *rule, bool verbose = false, IResponseFilter *pFilter = NULL ); - bool ResolveResponse( ResponseSearchResult& result, int depth, const char *name, bool verbose = false, IResponseFilter *pFilter = NULL ); - int SelectWeightedResponseFromResponseGroup( ResponseGroup *g, IResponseFilter *pFilter ); - void DescribeResponseGroup( ResponseGroup *group, int selected, int depth ); - void DebugPrint( int depth, const char *fmt, ... ); - - void LoadFromBuffer( const char *scriptfile, const char *buffer, CStringPool &includedFiles ); - - void GetCurrentScript( char *buf, size_t buflen ); - int GetCurrentToken() const; - void SetCurrentScript( const char *script ); - bool IsRootCommand(); - - void PushScript( const char *scriptfile, unsigned char *buffer ); - void PopScript(void); - - void ResponseWarning( const char *fmt, ... ); - - CUtlDict< ResponseGroup, short > m_Responses; - CUtlDict< Criteria, short > m_Criteria; - CUtlDict< Rule, short > m_Rules; - CUtlDict< Enumeration, short > m_Enumerations; - - char token[ 1204 ]; - - bool m_bUnget; - bool m_bPrecache; - - bool m_bCustomManagable; - - struct ScriptEntry - { - unsigned char *buffer; - FileNameHandle_t name; - const char *currenttoken; - int tokencount; - }; - - CUtlVector< ScriptEntry > m_ScriptStack; - - friend class CDefaultResponseSystemSaveRestoreBlockHandler; - friend class CResponseSystemSaveRestoreOps; -}; - -BEGIN_SIMPLE_DATADESC( Response ) - // DEFINE_FIELD( type, FIELD_INTEGER ), - // DEFINE_ARRAY( value, FIELD_CHARACTER ), - // DEFINE_FIELD( weight, FIELD_FLOAT ), - DEFINE_FIELD( depletioncount, FIELD_CHARACTER ), - // DEFINE_FIELD( first, FIELD_BOOLEAN ), - // DEFINE_FIELD( last, FIELD_BOOLEAN ), -END_DATADESC() - -BEGIN_SIMPLE_DATADESC( ResponseGroup ) - // DEFINE_FIELD( group, FIELD_UTLVECTOR ), - // DEFINE_FIELD( rp, FIELD_EMBEDDED ), - // DEFINE_FIELD( m_bDepleteBeforeRepeat, FIELD_BOOLEAN ), - DEFINE_FIELD( m_nDepletionCount, FIELD_CHARACTER ), - // DEFINE_FIELD( m_bHasFirst, FIELD_BOOLEAN ), - // DEFINE_FIELD( m_bHasLast, FIELD_BOOLEAN ), - // DEFINE_FIELD( m_bSequential, FIELD_BOOLEAN ), - // DEFINE_FIELD( m_bNoRepeat, FIELD_BOOLEAN ), - DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), - DEFINE_FIELD( m_nCurrentIndex, FIELD_CHARACTER ), -END_DATADESC() - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CResponseSystem::CResponseSystem() -{ - token[0] = 0; - m_bUnget = false; - m_bPrecache = true; - m_bCustomManagable = false; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CResponseSystem::~CResponseSystem() -{ -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : char const -//----------------------------------------------------------------------------- -void CResponseSystem::GetCurrentScript( char *buf, size_t buflen ) -{ - Assert( buf ); - buf[ 0 ] = 0; - if ( m_ScriptStack.Count() <= 0 ) - return; - - if ( filesystem->String( m_ScriptStack[ 0 ].name, buf, buflen ) ) - { - return; - } - buf[ 0 ] = 0; -} - -void CResponseSystem::PushScript( const char *scriptfile, unsigned char *buffer ) -{ - ScriptEntry e; - e.name = filesystem->FindOrAddFileName( scriptfile ); - e.buffer = buffer; - e.currenttoken = (char *)e.buffer; - e.tokencount = 0; - m_ScriptStack.AddToHead( e ); -} - -void CResponseSystem::PopScript(void) -{ - Assert( m_ScriptStack.Count() >= 1 ); - if ( m_ScriptStack.Count() <= 0 ) - return; - - m_ScriptStack.Remove( 0 ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CResponseSystem::Clear() -{ - m_Responses.RemoveAll(); - m_Criteria.RemoveAll(); - m_Rules.RemoveAll(); - m_Enumerations.RemoveAll(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -// found - -// Output : float -//----------------------------------------------------------------------------- -float CResponseSystem::LookupEnumeration( const char *name, bool& found ) -{ - int idx = m_Enumerations.Find( name ); - if ( idx == m_Enumerations.InvalidIndex() ) - { - found = false; - return 0.0f; - } - - - found = true; - return m_Enumerations[ idx ].value; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : matcher - -//----------------------------------------------------------------------------- -void CResponseSystem::ResolveToken( Matcher& matcher, char *token, size_t bufsize, char const *rawtoken ) -{ - if ( rawtoken[0] != '[' ) - { - Q_strncpy( token, rawtoken, bufsize ); - return; - } - - // Now lookup enumeration - bool found = false; - float f = LookupEnumeration( rawtoken, found ); - if ( !found ) - { - Q_strncpy( token, rawtoken, bufsize ); - ResponseWarning( "No such enumeration '%s'\n", token ); - return; - } - - Q_snprintf( token, bufsize, "%f", f ); -} - - -static bool AppearsToBeANumber( char const *token ) -{ - if ( atof( token ) != 0.0f ) - return true; - - char const *p = token; - while ( *p ) - { - if ( *p != '0' ) - return false; - - p++; - } - - return true; -} - -void CResponseSystem::ComputeMatcher( Criteria *c, Matcher& matcher ) -{ - const char *s = c->value; - if ( !s ) - { - matcher.valid = false; - return; - } - - const char *in = s; - - char token[ 128 ]; - char rawtoken[ 128 ]; - - token[ 0 ] = 0; - rawtoken[ 0 ] = 0; - - int n = 0; - - bool gt = false; - bool lt = false; - bool eq = false; - bool nt = false; - - bool done = false; - while ( !done ) - { - switch( *in ) - { - case '>': - { - gt = true; - Assert( !lt ); // Can't be both - } - break; - case '<': - { - lt = true; - Assert( !gt ); // Can't be both - } - break; - case '=': - { - eq = true; - } - break; - case ',': - case '\0': - { - rawtoken[ n ] = 0; - n = 0; - - // Convert raw token to real token in case token is an enumerated type specifier - ResolveToken( matcher, token, sizeof( token ), rawtoken ); - - // Fill in first data set - if ( gt ) - { - matcher.usemin = true; - matcher.minequals = eq; - matcher.minval = (float)atof( token ); - - matcher.isnumeric = true; - } - else if ( lt ) - { - matcher.usemax = true; - matcher.maxequals = eq; - matcher.maxval = (float)atof( token ); - - matcher.isnumeric = true; - } - else - { - if ( *in == ',' ) - { - // If there's a comma, this better have been a less than or a gt key - Assert( 0 ); - } - - matcher.notequal = nt; - - matcher.isnumeric = AppearsToBeANumber( token ); - } - - gt = lt = eq = nt = false; - - if ( !(*in) ) - { - done = true; - } - } - break; - case '!': - nt = true; - break; - default: - rawtoken[ n++ ] = *in; - break; - } - - in++; - } - - matcher.SetToken( token ); - matcher.SetRaw( rawtoken ); - matcher.valid = true; -} - -bool CResponseSystem::CompareUsingMatcher( const char *setValue, Matcher& m, bool verbose /*=false*/ ) -{ - if ( !m.valid ) - return false; - - float v = (float)atof( setValue ); - if ( setValue[0] == '[' ) - { - bool found = false; - v = LookupEnumeration( setValue, found ); - } - - int minmaxcount = 0; - - if ( m.usemin ) - { - if ( m.minequals ) - { - if ( v < m.minval ) - return false; - } - else - { - if ( v <= m.minval ) - return false; - } - - ++minmaxcount; - } - - if ( m.usemax ) - { - if ( m.maxequals ) - { - if ( v > m.maxval ) - return false; - } - else - { - if ( v >= m.maxval ) - return false; - } - - ++minmaxcount; - } - - // Had one or both criteria and met them - if ( minmaxcount >= 1 ) - { - return true; - } - - if ( m.notequal ) - { - if ( m.isnumeric ) - { - if ( v == (float)atof( m.GetToken() ) ) - return false; - } - else - { - if ( !Q_stricmp( setValue, m.GetToken() ) ) - return false; - } - - return true; - } - - if ( m.isnumeric ) - { - // If the setValue is "", the NPC doesn't have the key at all, - // in which case we shouldn't match "0". - if ( !setValue || !setValue[0] ) - return false; - - return v == (float)atof( m.GetToken() ); - } - - return !Q_stricmp( setValue, m.GetToken() ) ? true : false; -} - -bool CResponseSystem::Compare( const char *setValue, Criteria *c, bool verbose /*= false*/ ) -{ - Assert( c ); - Assert( setValue ); - - bool bret = CompareUsingMatcher( setValue, c->matcher, verbose ); - - if ( verbose ) - { - DevMsg( "'%20s' vs. '%20s' = ", setValue, c->value ); - - { - //DevMsg( "\n" ); - //m.Describe(); - } - } - return bret; -} - -float CResponseSystem::RecursiveScoreSubcriteriaAgainstRule( const AI_CriteriaSet& set, Criteria *parent, bool& exclude, bool verbose /*=false*/ ) -{ - float score = 0.0f; - int subcount = parent->subcriteria.Count(); - for ( int i = 0; i < subcount; i++ ) - { - int icriterion = parent->subcriteria[ i ]; - - bool excludesubrule = false; - if (verbose) - { - DevMsg( "\n" ); - } - score += ScoreCriteriaAgainstRuleCriteria( set, icriterion, excludesubrule, verbose ); - } - - exclude = ( parent->required && score == 0.0f ) ? true : false; - - return score * parent->weight.GetFloat(); -} - -float CResponseSystem::RecursiveLookForCriteria( const AI_CriteriaSet &criteriaSet, Criteria *pParent ) -{ - float flScore = 0.0f; - int nSubCount = pParent->subcriteria.Count(); - for ( int iSub = 0; iSub < nSubCount; ++iSub ) - { - int iCriteria = pParent->subcriteria[iSub]; - flScore += LookForCriteria( criteriaSet, iCriteria ); - } - - return flScore; -} - -float CResponseSystem::LookForCriteria( const AI_CriteriaSet &criteriaSet, int iCriteria ) -{ - Criteria *pCriteria = &m_Criteria[iCriteria]; - if ( pCriteria->IsSubCriteriaType() ) - { - return RecursiveLookForCriteria( criteriaSet, pCriteria ); - } - - int iIndex = criteriaSet.FindCriterionIndex( pCriteria->name ); - if ( iIndex == -1 ) - return 0.0f; - - Assert( criteriaSet.GetValue( iIndex ) ); - if ( Q_stricmp( criteriaSet.GetValue( iIndex ), pCriteria->value ) ) - return 0.0f; - - return 1.0f; -} - -float CResponseSystem::ScoreCriteriaAgainstRuleCriteria( const AI_CriteriaSet& set, int icriterion, bool& exclude, bool verbose /*=false*/ ) -{ - Criteria *c = &m_Criteria[ icriterion ]; - - if ( c->IsSubCriteriaType() ) - { - return RecursiveScoreSubcriteriaAgainstRule( set, c, exclude, verbose ); - } - - if ( verbose ) - { - DevMsg( " criterion '%25s':'%15s' ", m_Criteria.GetElementName( icriterion ), c->name ); - } - - exclude = false; - - float score = 0.0f; - - const char *actualValue = ""; - - int found = set.FindCriterionIndex( c->name ); - if ( found != -1 ) - { - actualValue = set.GetValue( found ); - if ( !actualValue ) - { - Assert( 0 ); - return score; - } - } - - Assert( actualValue ); - - if ( Compare( actualValue, c, verbose ) ) - { - float w = set.GetWeight( found ); - score = w * c->weight.GetFloat(); - - if ( verbose ) - { - DevMsg( "matched, weight %4.2f (s %4.2f x c %4.2f)", - score, w, c->weight.GetFloat() ); - } - } - else - { - if ( c->required ) - { - exclude = true; - if ( verbose ) - { - DevMsg( "failed (+exclude rule)" ); - } - } - else - { - if ( verbose ) - { - DevMsg( "failed" ); - } - } - } - - return score; -} - -float CResponseSystem::ScoreCriteriaAgainstRule( const AI_CriteriaSet& set, int irule, bool verbose /*=false*/ ) -{ - Rule *rule = &m_Rules[ irule ]; - float score = 0.0f; - - bool bBeingWatched = false; - - // See if we're trying to debug this rule - const char *pszText = rr_debugrule.GetString(); - if ( pszText && pszText[0] && !Q_stricmp( pszText, m_Rules.GetElementName( irule ) ) ) - { - bBeingWatched = true; - } - - if ( !rule->IsEnabled() ) - { - if ( bBeingWatched ) - { - DevMsg("Rule '%s' is disabled.\n", m_Rules.GetElementName( irule ) ); - } - return 0.0f; - } - - if ( bBeingWatched ) - { - verbose = true; - } - - if ( verbose ) - { - DevMsg( "Scoring rule '%s' (%i)\n{\n", m_Rules.GetElementName( irule ), irule+1 ); - } - - // Iterate set criteria - int count = rule->m_Criteria.Count(); - int i; - for ( i = 0; i < count; i++ ) - { - int icriterion = rule->m_Criteria[ i ]; - - bool exclude = false; - score += ScoreCriteriaAgainstRuleCriteria( set, icriterion, exclude, verbose ); - - if ( verbose ) - { - DevMsg( ", score %4.2f\n", score ); - } - - if ( exclude ) - { - score = 0.0f; - break; - } - } - - if ( verbose ) - { - DevMsg( "}\n" ); - } - - return score; -} - -void CResponseSystem::DebugPrint( int depth, const char *fmt, ... ) -{ - int indentchars = 3 * depth; - char *indent = (char *)_alloca( indentchars + 1); - indent[ indentchars ] = 0; - while ( --indentchars >= 0 ) - { - indent[ indentchars ] = ' '; - } - - // Dump text to debugging console. - va_list argptr; - char szText[1024]; - - va_start (argptr, fmt); - Q_vsnprintf (szText, sizeof( szText ), fmt, argptr); - va_end (argptr); - - DevMsg( "%s%s", indent, szText ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CResponseSystem::ResetResponseGroups() -{ - int i; - int c = m_Responses.Count(); - for ( i = 0; i < c; i++ ) - { - m_Responses[ i ].Reset(); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *g - -// Output : int -//----------------------------------------------------------------------------- -int CResponseSystem::SelectWeightedResponseFromResponseGroup( ResponseGroup *g, IResponseFilter *pFilter ) -{ - int c = g->group.Count(); - if ( !c ) - { - Assert( !"Expecting response group with >= 1 elements" ); - return -1; - } - - int i; - - // Fake depletion of unavailable choices - CUtlVector fakedDepletes; - if ( pFilter && g->ShouldCheckRepeats() ) - { - for ( i = 0; i < c; i++ ) - { - Response *r = &g->group[ i ]; - if ( r->depletioncount != g->GetDepletionCount() && !pFilter->IsValidResponse( r->GetType(), r->value ) ) - { - fakedDepletes.AddToTail( i ); - g->MarkResponseUsed( i ); - } - } - } - - if ( !g->HasUndepletedChoices() ) - { - g->ResetDepletionCount(); - - if ( pFilter && g->ShouldCheckRepeats() ) - { - fakedDepletes.RemoveAll(); - for ( i = 0; i < c; i++ ) - { - Response *r = &g->group[ i ]; - if ( !pFilter->IsValidResponse( r->GetType(), r->value ) ) - { - fakedDepletes.AddToTail( i ); - g->MarkResponseUsed( i ); - } - } - } - - if ( !g->HasUndepletedChoices() ) - return -1; - - // Disable the group if we looped through all the way - if ( g->IsNoRepeat() ) - { - g->SetEnabled( false ); - return -1; - } - } - - bool checkrepeats = g->ShouldCheckRepeats(); - int depletioncount = g->GetDepletionCount(); - - float totalweight = 0.0f; - int slot = -1; - - if ( checkrepeats ) - { - int check= -1; - // Snag the first slot right away - if ( g->HasUndepletedFirst( check ) && check != -1 ) - { - slot = check; - } - - if ( slot == -1 && g->HasUndepletedLast( check ) && check != -1 ) - { - // If this is the only undepleted one, use it now - for ( i = 0; i < c; i++ ) - { - Response *r = &g->group[ i ]; - if ( checkrepeats && - ( r->depletioncount == depletioncount ) ) - { - continue; - } - - if ( r->last ) - { - Assert( i == check ); - continue; - } - - // There's still another undepleted entry - break; - } - - // No more undepleted so use the r->last slot - if ( i >= c ) - { - slot = check; - } - } - } - - if ( slot == -1 ) - { - for ( i = 0; i < c; i++ ) - { - Response *r = &g->group[ i ]; - if ( checkrepeats && - ( r->depletioncount == depletioncount ) ) - { - continue; - } - - // Always skip last entry here since we will deal with it above - if ( checkrepeats && r->last ) - continue; - - int prevSlot = slot; - - if ( !totalweight ) - { - slot = i; - } - - // Always assume very first slot will match - totalweight += r->weight.GetFloat(); - if ( !totalweight || random->RandomFloat(0,totalweight) < r->weight.GetFloat() ) - { - slot = i; - } - - if ( !checkrepeats && slot != prevSlot && pFilter && !pFilter->IsValidResponse( r->GetType(), r->value ) ) - { - slot = prevSlot; - totalweight -= r->weight.GetFloat(); - } - } - } - - if ( slot != -1 ) - g->MarkResponseUsed( slot ); - - // Revert fake depletion of unavailable choices - if ( pFilter && g->ShouldCheckRepeats() ) - { - for ( i = 0; i < fakedDepletes.Count(); i++ ) - { - g->group[ fakedDepletes[ i ] ].depletioncount = 0;; - } - } - - return slot; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : searchResult - -// depth - -// *name - -// verbose - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CResponseSystem::ResolveResponse( ResponseSearchResult& searchResult, int depth, const char *name, bool verbose /*= false*/, IResponseFilter *pFilter ) -{ - int responseIndex = m_Responses.Find( name ); - if ( responseIndex == m_Responses.InvalidIndex() ) - return false; - - ResponseGroup *g = &m_Responses[ responseIndex ]; - // Group has been disabled - if ( !g->IsEnabled() ) - return false; - - int c = g->group.Count(); - if ( !c ) - return false; - - int idx = 0; - - if ( g->IsSequential() ) - { - // See if next index is valid - int initialIndex = g->GetCurrentIndex(); - bool bFoundValid = false; - - do - { - idx = g->GetCurrentIndex(); - g->SetCurrentIndex( idx + 1 ); - if ( idx >= c ) - { - if ( g->IsNoRepeat() ) - { - g->SetEnabled( false ); - return false; - } - idx = 0; - g->SetCurrentIndex( 0 ); - } - - if ( !pFilter || pFilter->IsValidResponse( g->group[idx].GetType(), g->group[idx].value ) ) - { - bFoundValid = true; - break; - } - - } while ( g->GetCurrentIndex() != initialIndex ); - - if ( !bFoundValid ) - return false; - } - else - { - idx = SelectWeightedResponseFromResponseGroup( g, pFilter ); - if ( idx < 0 ) - return false; - } - - if ( verbose ) - { - DebugPrint( depth, "%s\n", m_Responses.GetElementName( responseIndex ) ); - DebugPrint( depth, "{\n" ); - DescribeResponseGroup( g, idx, depth ); - } - - bool bret = true; - - Response *result = &g->group[ idx ]; - if ( result->type == RESPONSE_RESPONSE ) - { - // Recurse - bret = ResolveResponse( searchResult, depth + 1, result->value, verbose, pFilter ); - } - else - { - searchResult.action = result; - searchResult.group = g; - } - - if( verbose ) - { - DebugPrint( depth, "}\n" ); - } - - return bret; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *group - -// selected - -// depth - -//----------------------------------------------------------------------------- -void CResponseSystem::DescribeResponseGroup( ResponseGroup *group, int selected, int depth ) -{ - int c = group->group.Count(); - - for ( int i = 0; i < c ; i++ ) - { - Response *r = &group->group[ i ]; - DebugPrint( depth + 1, "%s%20s : %40s %5.3f\n", - i == selected ? "-> " : " ", - AI_Response::DescribeResponse( r->GetType() ), - r->value, - r->weight.GetFloat() ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *rule - -// Output : CResponseSystem::Response -//----------------------------------------------------------------------------- -bool CResponseSystem::GetBestResponse( ResponseSearchResult& searchResult, Rule *rule, bool verbose /*=false*/, IResponseFilter *pFilter ) -{ - int c = rule->m_Responses.Count(); - if ( !c ) - return false; - - int index = random->RandomInt( 0, c - 1 ); - int groupIndex = rule->m_Responses[ index ]; - - ResponseGroup *g = &m_Responses[ groupIndex ]; - - // Group has been disabled - if ( !g->IsEnabled() ) - return false; - - int count = g->group.Count(); - if ( !count ) - return false; - - int responseIndex = 0; - - if ( g->IsSequential() ) - { - // See if next index is valid - int initialIndex = g->GetCurrentIndex(); - bool bFoundValid = false; - - do - { - responseIndex = g->GetCurrentIndex(); - g->SetCurrentIndex( responseIndex + 1 ); - if ( responseIndex >= count ) - { - if ( g->IsNoRepeat() ) - { - g->SetEnabled( false ); - return false; - } - responseIndex = 0; - g->SetCurrentIndex( 0 ); - } - - if ( !pFilter || pFilter->IsValidResponse( g->group[responseIndex].GetType(), g->group[responseIndex].value ) ) - { - bFoundValid = true; - break; - } - - } while ( g->GetCurrentIndex() != initialIndex ); - - if ( !bFoundValid ) - return false; - } - else - { - responseIndex = SelectWeightedResponseFromResponseGroup( g, pFilter ); - if ( responseIndex < 0 ) - return false; - } - - - Response *r = &g->group[ responseIndex ]; - - int depth = 0; - - if ( verbose ) - { - DebugPrint( depth, "%s\n", m_Responses.GetElementName( groupIndex ) ); - DebugPrint( depth, "{\n" ); - - DescribeResponseGroup( g, responseIndex, depth ); - } - - bool bret = true; - - if ( r->type == RESPONSE_RESPONSE ) - { - bret = ResolveResponse( searchResult, depth + 1, r->value, verbose, pFilter ); - } - else - { - searchResult.action = r; - searchResult.group = g; - } - - if ( verbose ) - { - DebugPrint( depth, "}\n" ); - } - - return bret; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : set - -// verbose - -// Output : int -//----------------------------------------------------------------------------- -int CResponseSystem::FindBestMatchingRule( const AI_CriteriaSet& set, bool verbose ) -{ - CUtlVector< int > bestrules; - float bestscore = 0.001f; - - int c = m_Rules.Count(); - int i; - for ( i = 0; i < c; i++ ) - { - float score = ScoreCriteriaAgainstRule( set, i, verbose ); - // Check equals so that we keep track of all matching rules - if ( score >= bestscore ) - { - // Reset bucket - if( score != bestscore ) - { - bestscore = score; - bestrules.RemoveAll(); - } - - // Add to bucket - bestrules.AddToTail( i ); - } - } - - int bestCount = bestrules.Count(); - if ( bestCount <= 0 ) - return -1; - - if ( bestCount == 1 ) - return bestrules[ 0 ]; - - // Randomly pick one of the tied matching rules - int idx = random->RandomInt( 0, bestCount - 1 ); - if ( verbose ) - { - DevMsg( "Found %i matching rules, selecting slot %i\n", bestCount, idx ); - } - return bestrules[ idx ]; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : set - -// Output : AI_Response -//----------------------------------------------------------------------------- -bool CResponseSystem::FindBestResponse( const AI_CriteriaSet& set, AI_Response& response, IResponseFilter *pFilter ) -{ - bool valid = false; - - int iDbgResponse = rr_debugresponses.GetInt(); - bool showRules = ( iDbgResponse == 2 ); - bool showResult = ( iDbgResponse == 1 || iDbgResponse == 2 ); - - // Look for match. verbose mode used to be at level 2, but disabled because the writers don't actually care for that info. - int bestRule = FindBestMatchingRule( set, iDbgResponse == 3 ); - - ResponseType_t responseType = RESPONSE_NONE; - AI_ResponseParams rp; - - char ruleName[ 128 ]; - char responseName[ 128 ]; - const char *context; - bool bcontexttoworld; - ruleName[ 0 ] = 0; - responseName[ 0 ] = 0; - context = NULL; - bcontexttoworld = false; - if ( bestRule != -1 ) - { - Rule *r = &m_Rules[ bestRule ]; - - ResponseSearchResult result; - if ( GetBestResponse( result, r, showResult, pFilter ) ) - { - Q_strncpy( responseName, result.action->value, sizeof( responseName ) ); - responseType = result.action->GetType(); - rp = result.group->rp; - } - - Q_strncpy( ruleName, m_Rules.GetElementName( bestRule ), sizeof( ruleName ) ); - - // Disable the rule if it only allows for matching one time - if ( r->IsMatchOnce() ) - { - r->Disable(); - } - context = r->GetContext(); - bcontexttoworld = r->IsApplyContextToWorld(); - - valid = true; - } - - response.Init( responseType, responseName, set, rp, ruleName, context, bcontexttoworld ); - - if ( showResult ) - { - /* - // clipped -- chet doesn't really want this info - if ( valid ) - { - // Rescore the winner and dump to console - ScoreCriteriaAgainstRule( set, bestRule, true ); - } - */ - - - if ( valid || showRules ) - { - // Describe the response, too - response.Describe(); - } - } - - return valid; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CResponseSystem::GetAllResponses( CUtlVector *pResponses ) -{ - for ( int i = 0; i < (int)m_Responses.Count(); i++ ) - { - ResponseGroup &group = m_Responses[i]; - - for ( int j = 0; j < group.group.Count(); j++) - { - Response &response = group.group[j]; - if ( response.type != RESPONSE_RESPONSE ) - { - AI_Response *pResponse = new AI_Response; - pResponse->Init( response.GetType(), response.value, AI_CriteriaSet(), group.rp, NULL, NULL, false ); - pResponses->AddToTail(pResponse); - } - } - } -} - -static void TouchFile( char const *pchFileName ) -{ - filesystem->Size( pchFileName ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CResponseSystem::Precache() -{ - bool bTouchFiles = CommandLine()->FindParm( "-makereslists" ) != 0; - - // enumerate and mark all the scripts so we know they're referenced - for ( int i = 0; i < (int)m_Responses.Count(); i++ ) - { - ResponseGroup &group = m_Responses[i]; - - for ( int j = 0; j < group.group.Count(); j++) - { - Response &response = group.group[j]; - - switch ( response.type ) - { - default: - break; - case RESPONSE_SCENE: - { - // fixup $gender references - char file[_MAX_PATH]; - Q_strncpy( file, response.value, sizeof(file) ); - char *gender = strstr( file, "$gender" ); - if ( gender ) - { - // replace with male & female - const char *postGender = gender + strlen("$gender"); - *gender = 0; - char genderFile[_MAX_PATH]; - // male - Q_snprintf( genderFile, sizeof(genderFile), "%smale%s", file, postGender); - - PrecacheInstancedScene( genderFile ); - if ( bTouchFiles ) - { - TouchFile( genderFile ); - } - - Q_snprintf( genderFile, sizeof(genderFile), "%sfemale%s", file, postGender); - - PrecacheInstancedScene( genderFile ); - if ( bTouchFiles ) - { - TouchFile( genderFile ); - } - } - else - { - PrecacheInstancedScene( file ); - if ( bTouchFiles ) - { - TouchFile( file ); - } - } - } - break; - case RESPONSE_SPEAK: - { - CBaseEntity::PrecacheScriptSound( response.value ); - } - break; - } - } - } -} - -void CResponseSystem::ParseInclude( CStringPool &includedFiles ) -{ - char includefile[ 256 ]; - ParseToken(); - Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token ); - - // check if the file is already included - if ( includedFiles.Find( includefile ) != NULL ) - { - return; - } - - MEM_ALLOC_CREDIT(); - - // Try and load it - CUtlBuffer buf; - if ( !filesystem->ReadFile( includefile, "GAME", buf ) ) - { - DevMsg( "Unable to load #included script %s\n", includefile ); - return; - } - - LoadFromBuffer( includefile, (const char *)buf.PeekGet(), includedFiles ); -} - -void CResponseSystem::LoadFromBuffer( const char *scriptfile, const char *buffer, CStringPool &includedFiles ) -{ - includedFiles.Allocate( scriptfile ); - PushScript( scriptfile, (unsigned char * )buffer ); - - if( rr_dumpresponses.GetBool() ) - { - DevMsg("Reading: %s\n", scriptfile ); - } - - while ( 1 ) - { - ParseToken(); - if ( !token[0] ) - { - break; - } - - if ( !Q_stricmp( token, "#include" ) ) - { - ParseInclude( includedFiles ); - } - else if ( !Q_stricmp( token, "response" ) ) - { - ParseResponse(); - } - else if ( !Q_stricmp( token, "criterion" ) || - !Q_stricmp( token, "criteria" ) ) - { - ParseCriterion(); - } - else if ( !Q_stricmp( token, "rule" ) ) - { - ParseRule(); - } - else if ( !Q_stricmp( token, "enumeration" ) ) - { - ParseEnumeration(); - } - else - { - int byteoffset = m_ScriptStack[ 0 ].currenttoken - (const char *)m_ScriptStack[ 0 ].buffer; - - Error( "CResponseSystem::LoadFromBuffer: Unknown entry type '%s', expecting 'response', 'criterion', 'enumeration' or 'rules' in file %s(offset:%i)\n", - token, scriptfile, byteoffset ); - break; - } - } - - if ( m_ScriptStack.Count() == 1 ) - { - char cur[ 256 ]; - GetCurrentScript( cur, sizeof( cur ) ); - DevMsg( 1, "CResponseSystem: %s (%i rules, %i criteria, and %i responses)\n", - cur, m_Rules.Count(), m_Criteria.Count(), m_Responses.Count() ); - - if( rr_dumpresponses.GetBool() ) - { - DumpRules(); - } - } - - PopScript(); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CResponseSystem::LoadRuleSet( const char *basescript ) -{ - int length = 0; - unsigned char *buffer = (unsigned char *)UTIL_LoadFileForMe( basescript, &length ); - if ( length <= 0 || !buffer ) - { - DevMsg( 1, "CResponseSystem: failed to load %s\n", basescript ); - return; - } - - CStringPool includedFiles; - - LoadFromBuffer( basescript, (const char *)buffer, includedFiles ); - - UTIL_FreeFile( buffer ); - - Assert( m_ScriptStack.Count() == 0 ); -} - -static ResponseType_t ComputeResponseType( const char *s ) -{ - if ( !Q_stricmp( s, "scene" ) ) - { - return RESPONSE_SCENE; - } - else if ( !Q_stricmp( s, "sentence" ) ) - { - return RESPONSE_SENTENCE; - } - else if ( !Q_stricmp( s, "speak" ) ) - { - return RESPONSE_SPEAK; - } - else if ( !Q_stricmp( s, "response" ) ) - { - return RESPONSE_RESPONSE; - } - else if ( !Q_stricmp( s, "print" ) ) - { - return RESPONSE_PRINT; - } - - return RESPONSE_NONE; -} - -void CResponseSystem::ParseOneResponse( const char *responseGroupName, ResponseGroup& group ) -{ - Response newResponse; - newResponse.weight.SetFloat( 1.0f ); - AI_ResponseParams *rp = &group.rp; - - newResponse.type = ComputeResponseType( token ); - if ( RESPONSE_NONE == newResponse.type ) - { - ResponseWarning( "response entry '%s' with unknown response type '%s'\n", responseGroupName, token ); - return; - } - - ParseToken(); - newResponse.value = CopyString( token ); - - while ( TokenWaiting() ) - { - ParseToken(); - if ( !Q_stricmp( token, "weight" ) ) - { - ParseToken(); - newResponse.weight.SetFloat( (float)atof( token ) ); - continue; - } - - if ( !Q_stricmp( token, "predelay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_DELAYBEFORESPEAK; - rp->predelay.FromInterval( ReadInterval( token ) ); - continue; - } - - if ( !Q_stricmp( token, "nodelay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; - rp->delay.start = 0; - rp->delay.range = 0; - continue; - } - - if ( !Q_stricmp( token, "defaultdelay" ) ) - { - rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; - rp->delay.start = AIS_DEF_MIN_DELAY; - rp->delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY ); - continue; - } - - if ( !Q_stricmp( token, "delay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; - rp->delay.FromInterval( ReadInterval( token ) ); - continue; - } - - if ( !Q_stricmp( token, "speakonce" ) ) - { - rp->flags |= AI_ResponseParams::RG_SPEAKONCE; - continue; - } - - if ( !Q_stricmp( token, "noscene" ) ) - { - rp->flags |= AI_ResponseParams::RG_DONT_USE_SCENE; - continue; - } - - if ( !Q_stricmp( token, "stop_on_nonidle" ) ) - { - rp->flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE; - continue; - } - - if ( !Q_stricmp( token, "odds" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_ODDS; - rp->odds = clamp( atoi( token ), 0, 100 ); - continue; - } - - if ( !Q_stricmp( token, "respeakdelay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_RESPEAKDELAY; - rp->respeakdelay.FromInterval( ReadInterval( token ) ); - continue; - } - - if ( !Q_stricmp( token, "weapondelay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_WEAPONDELAY; - rp->weapondelay.FromInterval( ReadInterval( token ) ); - continue; - } - - if ( !Q_stricmp( token, "soundlevel" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_SOUNDLEVEL; - rp->soundlevel = (soundlevel_t)TextToSoundLevel( token ); - continue; - } - - if ( !Q_stricmp( token, "displayfirst" ) ) - { - newResponse.first = true; - group.m_bHasFirst = true; - continue; - } - - if ( !Q_stricmp( token, "displaylast" ) ) - { - newResponse.last = true; - group.m_bHasLast= true; - continue; - } - - ResponseWarning( "response entry '%s' with unknown command '%s'\n", responseGroupName, token ); - } - - group.group.AddToTail( newResponse ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CResponseSystem::IsRootCommand() -{ - if ( !Q_stricmp( token, "#include" ) ) - return true; - if ( !Q_stricmp( token, "response" ) ) - return true; - if ( !Q_stricmp( token, "enumeration" ) ) - return true; - if ( !Q_stricmp( token, "criteria" ) ) - return true; - if ( !Q_stricmp( token, "criterion" ) ) - return true; - if ( !Q_stricmp( token, "rule" ) ) - return true; - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *kv - -//----------------------------------------------------------------------------- -void CResponseSystem::ParseResponse( void ) -{ - // Should have groupname at start - char responseGroupName[ 128 ]; - - ResponseGroup newGroup; - AI_ResponseParams *rp = &newGroup.rp; - - // Response Group Name - ParseToken(); - Q_strncpy( responseGroupName, token, sizeof( responseGroupName ) ); - - while ( 1 ) - { - ParseToken(); - - // Oops, part of next definition - if( IsRootCommand() ) - { - Unget(); - break; - } - - if ( !Q_stricmp( token, "{" ) ) - { - while ( 1 ) - { - ParseToken(); - if ( !Q_stricmp( token, "}" ) ) - break; - - if ( !Q_stricmp( token, "permitrepeats" ) ) - { - newGroup.m_bDepleteBeforeRepeat = false; - continue; - } - else if ( !Q_stricmp( token, "sequential" ) ) - { - newGroup.SetSequential( true ); - continue; - } - else if ( !Q_stricmp( token, "norepeat" ) ) - { - newGroup.SetNoRepeat( true ); - continue; - } - - ParseOneResponse( responseGroupName, newGroup ); - } - break; - } - - if ( !Q_stricmp( token, "predelay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_DELAYBEFORESPEAK; - rp->predelay.FromInterval( ReadInterval( token ) ); - continue; - } - - if ( !Q_stricmp( token, "nodelay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; - rp->delay.start = 0; - rp->delay.range = 0; - continue; - } - - if ( !Q_stricmp( token, "defaultdelay" ) ) - { - rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; - rp->delay.start = AIS_DEF_MIN_DELAY; - rp->delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY ); - continue; - } - - if ( !Q_stricmp( token, "delay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; - rp->delay.FromInterval( ReadInterval( token ) ); - continue; - } - - if ( !Q_stricmp( token, "speakonce" ) ) - { - rp->flags |= AI_ResponseParams::RG_SPEAKONCE; - continue; - } - - if ( !Q_stricmp( token, "noscene" ) ) - { - rp->flags |= AI_ResponseParams::RG_DONT_USE_SCENE; - continue; - } - - if ( !Q_stricmp( token, "stop_on_nonidle" ) ) - { - rp->flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE; - continue; - } - - if ( !Q_stricmp( token, "odds" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_ODDS; - rp->odds = clamp( atoi( token ), 0, 100 ); - continue; - } - - if ( !Q_stricmp( token, "respeakdelay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_RESPEAKDELAY; - rp->respeakdelay.FromInterval( ReadInterval( token ) ); - continue; - } - - if ( !Q_stricmp( token, "weapondelay" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_WEAPONDELAY; - rp->weapondelay.FromInterval( ReadInterval( token ) ); - continue; - } - - if ( !Q_stricmp( token, "soundlevel" ) ) - { - ParseToken(); - rp->flags |= AI_ResponseParams::RG_SOUNDLEVEL; - rp->soundlevel = (soundlevel_t)TextToSoundLevel( token ); - continue; - } - - ParseOneResponse( responseGroupName, newGroup ); - } - - m_Responses.Insert( responseGroupName, newGroup ); -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *criterion - -//----------------------------------------------------------------------------- -int CResponseSystem::ParseOneCriterion( const char *criterionName ) -{ - char key[ 128 ]; - char value[ 128 ]; - - Criteria newCriterion; - - bool gotbody = false; - - while ( TokenWaiting() || !gotbody ) - { - ParseToken(); - - // Oops, part of next definition - if( IsRootCommand() ) - { - Unget(); - break; - } - - if ( !Q_stricmp( token, "{" ) ) - { - gotbody = true; - - while ( 1 ) - { - ParseToken(); - if ( !Q_stricmp( token, "}" ) ) - break; - - // Look up subcriteria index - int idx = m_Criteria.Find( token ); - if ( idx != m_Criteria.InvalidIndex() ) - { - newCriterion.subcriteria.AddToTail( idx ); - } - else - { - ResponseWarning( "Skipping unrecongized subcriterion '%s' in '%s'\n", token, criterionName ); - } - } - continue; - } - else if ( !Q_stricmp( token, "required" ) ) - { - newCriterion.required = true; - } - else if ( !Q_stricmp( token, "weight" ) ) - { - ParseToken(); - newCriterion.weight.SetFloat( (float)atof( token ) ); - } - else - { - Assert( newCriterion.subcriteria.Count() == 0 ); - - // Assume it's the math info for a non-subcriteria resposne - Q_strncpy( key, token, sizeof( key ) ); - ParseToken(); - Q_strncpy( value, token, sizeof( value ) ); - - newCriterion.name = CopyString( key ); - newCriterion.value = CopyString( value ); - - gotbody = true; - } - } - - if ( !newCriterion.IsSubCriteriaType() ) - { - ComputeMatcher( &newCriterion, newCriterion.matcher ); - } - - if ( m_Criteria.Find( criterionName ) != m_Criteria.InvalidIndex() ) - { - ResponseWarning( "Multiple definitions for criteria '%s'\n", criterionName ); - return m_Criteria.InvalidIndex(); - } - - int idx = m_Criteria.Insert( criterionName, newCriterion ); - return idx; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *kv - -//----------------------------------------------------------------------------- -void CResponseSystem::ParseCriterion( void ) -{ - // Should have groupname at start - char criterionName[ 128 ]; - ParseToken(); - Q_strncpy( criterionName, token, sizeof( criterionName ) ); - - ParseOneCriterion( criterionName ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *kv - -//----------------------------------------------------------------------------- -void CResponseSystem::ParseEnumeration( void ) -{ - char enumerationName[ 128 ]; - ParseToken(); - Q_strncpy( enumerationName, token, sizeof( enumerationName ) ); - - ParseToken(); - if ( Q_stricmp( token, "{" ) ) - { - ResponseWarning( "Expecting '{' in enumeration '%s', got '%s'\n", enumerationName, token ); - return; - } - - while ( 1 ) - { - ParseToken(); - if ( !Q_stricmp( token, "}" ) ) - break; - - if ( Q_strlen( token ) <= 0 ) - { - ResponseWarning( "Expecting more tokens in enumeration '%s'\n", enumerationName ); - break; - } - - char key[ 128 ]; - - Q_strncpy( key, token, sizeof( key ) ); - ParseToken(); - float value = (float)atof( token ); - - char sz[ 128 ]; - Q_snprintf( sz, sizeof( sz ), "[%s::%s]", enumerationName, key ); - Q_strlower( sz ); - - Enumeration newEnum; - newEnum.value = value; - - if ( m_Enumerations.Find( sz ) == m_Enumerations.InvalidIndex() ) - { - m_Enumerations.Insert( sz, newEnum ); - } - /* - else - { - ResponseWarning( "Ignoring duplication enumeration '%s'\n", sz ); - } - */ - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *kv - -//----------------------------------------------------------------------------- -void CResponseSystem::ParseRule( void ) -{ - static int instancedCriteria = 0; - - char ruleName[ 128 ]; - ParseToken(); - Q_strncpy( ruleName, token, sizeof( ruleName ) ); - - ParseToken(); - if ( Q_stricmp( token, "{" ) ) - { - ResponseWarning( "Expecting '{' in rule '%s', got '%s'\n", ruleName, token ); - return; - } - - // entries are "criteria", "response" or an in-line criteria to instance - Rule newRule; - - char sz[ 128 ]; - - bool validRule = true; - while ( 1 ) - { - ParseToken(); - if ( !Q_stricmp( token, "}" ) ) - { - break; - } - - if ( Q_strlen( token ) <= 0 ) - { - ResponseWarning( "Expecting more tokens in rule '%s'\n", ruleName ); - break; - } - - if ( !Q_stricmp( token, "matchonce" ) ) - { - newRule.m_bMatchOnce = true; - continue; - } - - if ( !Q_stricmp( token, "applyContextToWorld" ) ) - { - newRule.m_bApplyContextToWorld = true; - continue; - } - - if ( !Q_stricmp( token, "applyContext" ) ) - { - ParseToken(); - if ( newRule.GetContext() == NULL ) - { - newRule.SetContext( token ); - } - else - { - CFmtStrN<1024> newContext( "%s,%s", newRule.GetContext(), token ); - newRule.SetContext( newContext ); - } - continue; - } - - if ( !Q_stricmp( token, "response" ) ) - { - // Read them until we run out. - while ( TokenWaiting() ) - { - ParseToken(); - int idx = m_Responses.Find( token ); - if ( idx != m_Responses.InvalidIndex() ) - { - MEM_ALLOC_CREDIT(); - newRule.m_Responses.AddToTail( idx ); - } - else - { - validRule = false; - ResponseWarning( "No such response '%s' for rule '%s'\n", token, ruleName ); - } - } - continue; - } - - if ( !Q_stricmp( token, "criteria" ) || - !Q_stricmp( token, "criterion" ) ) - { - // Read them until we run out. - while ( TokenWaiting() ) - { - ParseToken(); - - int idx = m_Criteria.Find( token ); - if ( idx != m_Criteria.InvalidIndex() ) - { - MEM_ALLOC_CREDIT(); - newRule.m_Criteria.AddToTail( idx ); - } - else - { - validRule = false; - ResponseWarning( "No such criterion '%s' for rule '%s'\n", token, ruleName ); - } - } - continue; - } - - // It's an inline criteria, generate a name and parse it in - Q_snprintf( sz, sizeof( sz ), "[%s%03i]", ruleName, ++instancedCriteria ); - Unget(); - int idx = ParseOneCriterion( sz ); - if ( idx != m_Criteria.InvalidIndex() ) - { - newRule.m_Criteria.AddToTail( idx ); - } - } - - if ( validRule ) - { - m_Rules.Insert( ruleName, newRule ); - } - else - { - DevMsg( "Discarded rule %s\n", ruleName ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -int CResponseSystem::GetCurrentToken() const -{ - if ( m_ScriptStack.Count() <= 0 ) - return -1; - - return m_ScriptStack[ 0 ].tokencount; -} - - -void CResponseSystem::ResponseWarning( const char *fmt, ... ) -{ - va_list argptr; -#ifndef _XBOX - static char string[1024]; -#else - char string[1024]; -#endif - - va_start (argptr, fmt); - Q_vsnprintf(string, sizeof(string), fmt,argptr); - va_end (argptr); - - char cur[ 256 ]; - GetCurrentScript( cur, sizeof( cur ) ); - DevMsg( 1, "%s(token %i) : %s", cur, GetCurrentToken(), string ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CResponseSystem::CopyCriteriaFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ) -{ - // Add criteria from this rule to global list in custom response system. - int nCriteriaCount = pSrcRule->m_Criteria.Count(); - for ( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria ) - { - int iSrcIndex = pSrcRule->m_Criteria[iCriteria]; - Criteria *pSrcCriteria = &m_Criteria[iSrcIndex]; - if ( pSrcCriteria ) - { - int iIndex = pCustomSystem->m_Criteria.Find( m_Criteria.GetElementName( iSrcIndex ) ); - if ( iIndex != pCustomSystem->m_Criteria.InvalidIndex() ) - { - pDstRule->m_Criteria.AddToTail( iIndex ); - continue; - } - - // Add the criteria. - Criteria dstCriteria; - - dstCriteria.name = CopyString( pSrcCriteria->name ); - dstCriteria.value = CopyString( pSrcCriteria->value ); - dstCriteria.weight = pSrcCriteria->weight; - dstCriteria.required = pSrcCriteria->required; - dstCriteria.matcher = pSrcCriteria->matcher; - - int nSubCriteriaCount = pSrcCriteria->subcriteria.Count(); - for ( int iSubCriteria = 0; iSubCriteria < nSubCriteriaCount; ++iSubCriteria ) - { - int iSrcSubIndex = pSrcCriteria->subcriteria[iSubCriteria]; - Criteria *pSrcSubCriteria = &m_Criteria[iSrcSubIndex]; - if ( pSrcCriteria ) - { - int iSubIndex = pCustomSystem->m_Criteria.Find( pSrcSubCriteria->value ); - if ( iSubIndex != pCustomSystem->m_Criteria.InvalidIndex() ) - continue; - - // Add the criteria. - Criteria dstSubCriteria; - - dstSubCriteria.name = CopyString( pSrcSubCriteria->name ); - dstSubCriteria.value = CopyString( pSrcSubCriteria->value ); - dstSubCriteria.weight = pSrcSubCriteria->weight; - dstSubCriteria.required = pSrcSubCriteria->required; - dstSubCriteria.matcher = pSrcSubCriteria->matcher; - - int iSubInsertIndex = pCustomSystem->m_Criteria.Insert( pSrcSubCriteria->value, dstSubCriteria ); - dstCriteria.subcriteria.AddToTail( iSubInsertIndex ); - } - } - - int iInsertIndex = pCustomSystem->m_Criteria.Insert( m_Criteria.GetElementName( iSrcIndex ), dstCriteria ); - pDstRule->m_Criteria.AddToTail( iInsertIndex ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CResponseSystem::CopyResponsesFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ) -{ - // Add responses from this rule to global list in custom response system. - int nResponseGroupCount = pSrcRule->m_Responses.Count(); - for ( int iResponseGroup = 0; iResponseGroup < nResponseGroupCount; ++iResponseGroup ) - { - int iSrcResponseGroup = pSrcRule->m_Responses[iResponseGroup]; - ResponseGroup *pSrcResponseGroup = &m_Responses[iSrcResponseGroup]; - if ( pSrcResponseGroup ) - { - // Add response group. - ResponseGroup dstResponseGroup; - - dstResponseGroup.rp = pSrcResponseGroup->rp; - dstResponseGroup.m_bDepleteBeforeRepeat = pSrcResponseGroup->m_bDepleteBeforeRepeat; - dstResponseGroup.m_nDepletionCount = pSrcResponseGroup->m_nDepletionCount; - dstResponseGroup.m_bHasFirst = pSrcResponseGroup->m_bHasFirst; - dstResponseGroup.m_bHasLast = pSrcResponseGroup->m_bHasLast; - dstResponseGroup.m_bSequential = pSrcResponseGroup->m_bSequential; - dstResponseGroup.m_bNoRepeat = pSrcResponseGroup->m_bNoRepeat; - dstResponseGroup.m_bEnabled = pSrcResponseGroup->m_bEnabled; - dstResponseGroup.m_nCurrentIndex = pSrcResponseGroup->m_nCurrentIndex; - - int nSrcResponseCount = pSrcResponseGroup->group.Count(); - for ( int iResponse = 0; iResponse < nSrcResponseCount; ++iResponse ) - { - Response *pSrcResponse = &pSrcResponseGroup->group[iResponse]; - if ( pSrcResponse ) - { - // Add Response - Response dstResponse; - - dstResponse.weight = pSrcResponse->weight; - dstResponse.type = pSrcResponse->type; - dstResponse.value = CopyString( pSrcResponse->value ); - dstResponse.depletioncount = pSrcResponse->depletioncount; - dstResponse.first = pSrcResponse->first; - dstResponse.last = pSrcResponse->last; - - dstResponseGroup.group.AddToTail( dstResponse ); - } - } - - int iInsertIndex = pCustomSystem->m_Responses.Insert( m_Responses.GetElementName( iSrcResponseGroup ), dstResponseGroup ); - pDstRule->m_Responses.AddToTail( iInsertIndex ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CResponseSystem::CopyEnumerationsFrom( CResponseSystem *pCustomSystem ) -{ - int nEnumerationCount = m_Enumerations.Count(); - for ( int iEnumeration = 0; iEnumeration < nEnumerationCount; ++iEnumeration ) - { - Enumeration *pSrcEnumeration = &m_Enumerations[iEnumeration]; - if ( pSrcEnumeration ) - { - Enumeration dstEnumeration; - dstEnumeration.value = pSrcEnumeration->value; - pCustomSystem->m_Enumerations.Insert( m_Enumerations.GetElementName( iEnumeration ), dstEnumeration ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CResponseSystem::CopyRuleFrom( Rule *pSrcRule, int iRule, CResponseSystem *pCustomSystem ) -{ - // Verify data. - Assert( pSrcRule ); - Assert( pCustomSystem ); - if ( !pSrcRule || !pCustomSystem ) - return; - - // New rule - Rule dstRule; - - dstRule.SetContext( pSrcRule->GetContext() ); - dstRule.m_bMatchOnce = pSrcRule->m_bMatchOnce; - dstRule.m_bEnabled = pSrcRule->m_bEnabled; - dstRule.m_bApplyContextToWorld = pSrcRule->m_bApplyContextToWorld; - - // Copy off criteria. - CopyCriteriaFrom( pSrcRule, &dstRule, pCustomSystem ); - - // Copy off responses. - CopyResponsesFrom( pSrcRule, &dstRule, pCustomSystem ); - - // Copy off enumerations - Don't think we use these. -// CopyEnumerationsFrom( pCustomSystem ); - - // Add rule. - pCustomSystem->m_Rules.Insert( m_Rules.GetElementName( iRule ), dstRule ); -} - -//----------------------------------------------------------------------------- -// Purpose: A special purpose response system associated with a custom entity -//----------------------------------------------------------------------------- -class CInstancedResponseSystem : public CResponseSystem -{ - typedef CResponseSystem BaseClass; - -public: - CInstancedResponseSystem( const char *scriptfile ) : - m_pszScriptFile( 0 ) - { - Assert( scriptfile ); - - int len = Q_strlen( scriptfile ) + 1; - m_pszScriptFile = new char[ len ]; - Assert( m_pszScriptFile ); - Q_strncpy( m_pszScriptFile, scriptfile, len ); - } - - ~CInstancedResponseSystem() - { - delete[] m_pszScriptFile; - } - virtual const char *GetScriptFile( void ) - { - Assert( m_pszScriptFile ); - return m_pszScriptFile; - } - - // CAutoGameSystem - virtual bool Init() - { - const char *basescript = GetScriptFile(); - LoadRuleSet( basescript ); - return true; - } - - virtual void LevelInitPostEntity() - { - ResetResponseGroups(); - } - - virtual void Release() - { - Clear(); - delete this; - } -private: - - char *m_pszScriptFile; -}; - -//----------------------------------------------------------------------------- -// Purpose: The default response system for expressive AIs -//----------------------------------------------------------------------------- -class CDefaultResponseSystem : public CResponseSystem, public CAutoGameSystem -{ - typedef CAutoGameSystem BaseClass; - -public: - CDefaultResponseSystem() : CAutoGameSystem( "CDefaultResponseSystem" ) - { - } - - virtual const char *GetScriptFile( void ) - { - return "scripts/talker/response_rules.txt"; - } - - // CAutoServerSystem - virtual bool Init(); - virtual void Shutdown(); - - virtual void LevelInitPostEntity() - { - } - - virtual void Release() - { - Assert( 0 ); - } - - void AddInstancedResponseSystem( const char *scriptfile, CInstancedResponseSystem *sys ) - { - m_InstancedSystems.Insert( scriptfile, sys ); - } - - CInstancedResponseSystem *FindResponseSystem( const char *scriptfile ) - { - int idx = m_InstancedSystems.Find( scriptfile ); - if ( idx == m_InstancedSystems.InvalidIndex() ) - return NULL; - return m_InstancedSystems[ idx ]; - } - - IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ) - { - CInstancedResponseSystem *sys = ( CInstancedResponseSystem * )FindResponseSystem( scriptfile ); - if ( !sys ) - { - sys = new CInstancedResponseSystem( scriptfile ); - if ( !sys ) - { - Error( "Failed to load response system data from %s", scriptfile ); - } - - if ( !sys->Init() ) - { - Error( "CInstancedResponseSystem: Failed to init response system from %s!", scriptfile ); - } - - AddInstancedResponseSystem( scriptfile, sys ); - } - - sys->Precache(); - - return ( IResponseSystem * )sys; - } - - IResponseSystem *BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ); - void DestroyCustomResponseSystems(); - - virtual void LevelInitPreEntity() - { - // This will precache the default system - // All user installed systems are init'd by PrecacheCustomResponseSystem which will call sys->Precache() on the ones being used - - // FIXME: This is SLOW the first time you run the engine (can take 3 - 10 seconds!!!) - if ( ShouldPrecache() ) - { - Precache(); - } - - ResetResponseGroups(); - } - - void ReloadAllResponseSystems() - { - Clear(); - Init(); - - int c = m_InstancedSystems.Count(); - for ( int i = c - 1 ; i >= 0; i-- ) - { - CInstancedResponseSystem *sys = m_InstancedSystems[ i ]; - if ( !IsCustomManagable() ) - { - sys->Clear(); - sys->Init(); - } - else - { - // Custom reponse rules will manage/reload themselves - remove them. - m_InstancedSystems.RemoveAt( i ); - } - } - - } - -private: - - void ClearInstanced() - { - int c = m_InstancedSystems.Count(); - for ( int i = c - 1 ; i >= 0; i-- ) - { - CInstancedResponseSystem *sys = m_InstancedSystems[ i ]; - sys->Release(); - } - m_InstancedSystems.RemoveAll(); - } - - CUtlDict< CInstancedResponseSystem *, int > m_InstancedSystems; -}; - -IResponseSystem *CDefaultResponseSystem::BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ) -{ - // Create a instanced response system. - CInstancedResponseSystem *pCustomSystem = new CInstancedResponseSystem( pszCustomName ); - if ( !pCustomSystem ) - { - Error( "BuildCustomResponseSystemGivenCriterea: Failed to create custom response system %s!", pszCustomName ); - } - - pCustomSystem->Clear(); - - // Copy the relevant rules and data. - int nRuleCount = m_Rules.Count(); - for ( int iRule = 0; iRule < nRuleCount; ++iRule ) - { - Rule *pRule = &m_Rules[iRule]; - if ( pRule ) - { - float flScore = 0.0f; - - int nCriteriaCount = pRule->m_Criteria.Count(); - for ( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria ) - { - int iRuleCriteria = pRule->m_Criteria[iCriteria]; - - flScore += LookForCriteria( criteriaSet, iRuleCriteria ); - if ( flScore >= flCriteriaScore ) - { - CopyRuleFrom( pRule, iRule, pCustomSystem ); - break; - } - } - } - } - - // Set as a custom response system. - m_bCustomManagable = true; - AddInstancedResponseSystem( pszCustomName, pCustomSystem ); - -// pCustomSystem->DumpDictionary( pszCustomName ); - - return pCustomSystem; -} - -void CDefaultResponseSystem::DestroyCustomResponseSystems() -{ - ClearInstanced(); -} - - -static CDefaultResponseSystem defaultresponsesytem; -IResponseSystem *g_pResponseSystem = &defaultresponsesytem; - -CON_COMMAND( rr_reloadresponsesystems, "Reload all response system scripts." ) -{ - if ( !UTIL_IsCommandIssuedByServerAdmin() ) - return; - - defaultresponsesytem.ReloadAllResponseSystems(); - -#if defined( TF_DLL ) - // This is kind of hacky, but I need to get it in for now! - if( g_pGameRules->IsMultiplayer() ) - { - CMultiplayRules *pMultiplayRules = static_cast( g_pGameRules ); - pMultiplayRules->InitCustomResponseRulesDicts(); - } -#endif -} - -static short RESPONSESYSTEM_SAVE_RESTORE_VERSION = 1; - -// note: this won't save/restore settings from instanced response systems. Could add that with a CDefSaveRestoreOps implementation if needed -// -class CDefaultResponseSystemSaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler -{ -public: - const char *GetBlockName() - { - return "ResponseSystem"; - } - - void WriteSaveHeaders( ISave *pSave ) - { - pSave->WriteShort( &RESPONSESYSTEM_SAVE_RESTORE_VERSION ); - } - - void ReadRestoreHeaders( IRestore *pRestore ) - { - // No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so. - short version; - pRestore->ReadShort( &version ); - m_fDoLoad = ( version == RESPONSESYSTEM_SAVE_RESTORE_VERSION ); - } - - void Save( ISave *pSave ) - { - CDefaultResponseSystem& rs = defaultresponsesytem; - - int count = rs.m_Responses.Count(); - pSave->WriteInt( &count ); - for ( int i = 0; i < count; ++i ) - { - pSave->StartBlock( "ResponseGroup" ); - - pSave->WriteString( rs.m_Responses.GetElementName( i ) ); - const ResponseGroup *group = &rs.m_Responses[ i ]; - pSave->WriteAll( group ); - - short groupCount = group->group.Count(); - pSave->WriteShort( &groupCount ); - for ( int j = 0; j < groupCount; ++j ) - { - const Response *response = &group->group[ j ]; - pSave->StartBlock( "Response" ); - pSave->WriteString( response->value ); - pSave->WriteAll( response ); - pSave->EndBlock(); - } - - pSave->EndBlock(); - } - } - - void Restore( IRestore *pRestore, bool createPlayers ) - { - if ( !m_fDoLoad ) - return; - - CDefaultResponseSystem& rs = defaultresponsesytem; - - int count = pRestore->ReadInt(); - for ( int i = 0; i < count; ++i ) - { - char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF]; - pRestore->StartBlock( szResponseGroupBlockName ); - if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) ) - { - - char groupname[ 256 ]; - pRestore->ReadString( groupname, sizeof( groupname ), 0 ); - - // Try and find it - int idx = rs.m_Responses.Find( groupname ); - if ( idx != rs.m_Responses.InvalidIndex() ) - { - ResponseGroup *group = &rs.m_Responses[ idx ]; - pRestore->ReadAll( group ); - - short groupCount = pRestore->ReadShort(); - for ( int j = 0; j < groupCount; ++j ) - { - char szResponseBlockName[SIZE_BLOCK_NAME_BUF]; - - char responsename[ 256 ]; - pRestore->StartBlock( szResponseBlockName ); - if ( !Q_stricmp( szResponseBlockName, "Response" ) ) - { - pRestore->ReadString( responsename, sizeof( responsename ), 0 ); - - // Find it by name - int ri; - for ( ri = 0; ri < group->group.Count(); ++ri ) - { - Response *response = &group->group[ ri ]; - if ( !Q_stricmp( response->value, responsename ) ) - { - break; - } - } - - if ( ri < group->group.Count() ) - { - Response *response = &group->group[ ri ]; - pRestore->ReadAll( response ); - } - } - - pRestore->EndBlock(); - } - } - } - - pRestore->EndBlock(); - } - } -private: - - bool m_fDoLoad; - -} g_DefaultResponseSystemSaveRestoreBlockHandler; - -ISaveRestoreBlockHandler *GetDefaultResponseSystemSaveRestoreBlockHandler() -{ - return &g_DefaultResponseSystemSaveRestoreBlockHandler; -} - -//----------------------------------------------------------------------------- -// CResponseSystemSaveRestoreOps -// -// Purpose: Handles save and load for instanced response systems... -// -// BUGBUG: This will save the same response system to file multiple times for "shared" response systems and -// therefore it'll restore the same data onto the same pointer N times on reload (probably benign for now, but we could -// write code to save/restore the instanced ones by filename in the block handler above maybe? -//----------------------------------------------------------------------------- - -class CResponseSystemSaveRestoreOps : public CDefSaveRestoreOps -{ -public: - - virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) - { - CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField; - if ( !pRS || pRS == &defaultresponsesytem ) - return; - - int count = pRS->m_Responses.Count(); - pSave->WriteInt( &count ); - for ( int i = 0; i < count; ++i ) - { - pSave->StartBlock( "ResponseGroup" ); - - pSave->WriteString( pRS->m_Responses.GetElementName( i ) ); - const ResponseGroup *group = &pRS->m_Responses[ i ]; - pSave->WriteAll( group ); - - short groupCount = group->group.Count(); - pSave->WriteShort( &groupCount ); - for ( int j = 0; j < groupCount; ++j ) - { - const Response *response = &group->group[ j ]; - pSave->StartBlock( "Response" ); - pSave->WriteString( response->value ); - pSave->WriteAll( response ); - pSave->EndBlock(); - } - - pSave->EndBlock(); - } - } - - virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) - { - CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField; - if ( !pRS || pRS == &defaultresponsesytem ) - return; - - int count = pRestore->ReadInt(); - for ( int i = 0; i < count; ++i ) - { - char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF]; - pRestore->StartBlock( szResponseGroupBlockName ); - if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) ) - { - - char groupname[ 256 ]; - pRestore->ReadString( groupname, sizeof( groupname ), 0 ); - - // Try and find it - int idx = pRS->m_Responses.Find( groupname ); - if ( idx != pRS->m_Responses.InvalidIndex() ) - { - ResponseGroup *group = &pRS->m_Responses[ idx ]; - pRestore->ReadAll( group ); - - short groupCount = pRestore->ReadShort(); - for ( int j = 0; j < groupCount; ++j ) - { - char szResponseBlockName[SIZE_BLOCK_NAME_BUF]; - - char responsename[ 256 ]; - pRestore->StartBlock( szResponseBlockName ); - if ( !Q_stricmp( szResponseBlockName, "Response" ) ) - { - pRestore->ReadString( responsename, sizeof( responsename ), 0 ); - - // Find it by name - int ri; - for ( ri = 0; ri < group->group.Count(); ++ri ) - { - Response *response = &group->group[ ri ]; - if ( !Q_stricmp( response->value, responsename ) ) - { - break; - } - } - - if ( ri < group->group.Count() ) - { - Response *response = &group->group[ ri ]; - pRestore->ReadAll( response ); - } - } - - pRestore->EndBlock(); - } - } - } - - pRestore->EndBlock(); - } - } - -} g_ResponseSystemSaveRestoreOps; - -ISaveRestoreOps *responseSystemSaveRestoreOps = &g_ResponseSystemSaveRestoreOps; - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CDefaultResponseSystem::Init() -{ -/* - Warning( "sizeof( Response ) == %d\n", sizeof( Response ) ); - Warning( "sizeof( ResponseGroup ) == %d\n", sizeof( ResponseGroup ) ); - Warning( "sizeof( Criteria ) == %d\n", sizeof( Criteria ) ); - Warning( "sizeof( AI_ResponseParams ) == %d\n", sizeof( AI_ResponseParams ) ); -*/ - const char *basescript = GetScriptFile(); - - LoadRuleSet( basescript ); - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CDefaultResponseSystem::Shutdown() -{ - // Wipe instanced versions - ClearInstanced(); - - // Clear outselves - Clear(); - // IServerSystem chain - BaseClass::Shutdown(); -} - -//----------------------------------------------------------------------------- -// Purpose: Instance a custom response system -// Input : *scriptfile - -// Output : IResponseSystem -//----------------------------------------------------------------------------- -IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ) -{ - return defaultresponsesytem.PrecacheCustomResponseSystem( scriptfile ); -} - -//----------------------------------------------------------------------------- -// Purpose: Instance a custom response system -// Input : *scriptfile - -// set - -// Output : IResponseSystem -//----------------------------------------------------------------------------- -IResponseSystem *BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ) -{ - return defaultresponsesytem.BuildCustomResponseSystemGivenCriteria( pszBaseFile, pszCustomName, criteriaSet, flCriteriaScore ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void DestroyCustomResponseSystems() -{ - defaultresponsesytem.DestroyCustomResponseSystems(); -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CResponseSystem::DumpRules() -{ - int c = m_Rules.Count(); - int i; - - for ( i = 0; i < c; i++ ) - { - Msg("%s\n", m_Rules.GetElementName( i ) ); - } -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void CResponseSystem::DumpDictionary( const char *pszName ) -{ - Msg( "\nDictionary: %s\n", pszName ); - - int nRuleCount = m_Rules.Count(); - for ( int iRule = 0; iRule < nRuleCount; ++iRule ) - { - Msg(" Rule %d: %s\n", iRule, m_Rules.GetElementName( iRule ) ); - - Rule *pRule = &m_Rules[iRule]; - - int nCriteriaCount = pRule->m_Criteria.Count(); - for( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria ) - { - int iRuleCriteria = pRule->m_Criteria[iCriteria]; - Criteria *pCriteria = &m_Criteria[iRuleCriteria]; - Msg( " Criteria %d: %s %s\n", iCriteria, pCriteria->name, pCriteria->value ); - } - - int nResponseGroupCount = pRule->m_Responses.Count(); - for ( int iResponseGroup = 0; iResponseGroup < nResponseGroupCount; ++iResponseGroup ) - { - int iRuleResponse = pRule->m_Responses[iResponseGroup]; - ResponseGroup *pResponseGroup = &m_Responses[iRuleResponse]; - - Msg( " ResponseGroup %d: %s\n", iResponseGroup, m_Responses.GetElementName( iRuleResponse ) ); - - int nResponseCount = pResponseGroup->group.Count(); - for ( int iResponse = 0; iResponse < nResponseCount; ++iResponse ) - { - Response *pResponse = &pResponseGroup->group[iResponse]; - Msg( " Response %d: %s\n", iResponse, pResponse->value ); - } - } - } -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include "cbase.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "AI_ResponseSystem.h" +#include "igamesystem.h" +#include "AI_Criteria.h" +#include +#include "filesystem.h" +#include "utldict.h" +#include "ai_speech.h" +#include "tier0/icommandline.h" +#include +#include "sceneentity.h" +#include "isaverestore.h" +#include "utlbuffer.h" +#include "stringpool.h" +#include "fmtstr.h" +#include "multiplay_gamerules.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar rr_debugresponses( "rr_debugresponses", "0", FCVAR_NONE, "Show verbose matching output (1 for simple, 2 for rule scoring). If set to 3, it will only show response success/failure for npc_selected NPCs." ); +ConVar rr_debugrule( "rr_debugrule", "", FCVAR_NONE, "If set to the name of the rule, that rule's score will be shown whenever a concept is passed into the response rules system."); +ConVar rr_dumpresponses( "rr_dumpresponses", "0", FCVAR_NONE, "Dump all response_rules.txt and rules (requires restart)" ); + +static CUtlSymbolTable g_RS; + +inline static char *CopyString( const char *in ) +{ + if ( !in ) + return NULL; + + int len = Q_strlen( in ); + char *out = new char[ len + 1 ]; + Q_memcpy( out, in, len ); + out[ len ] = 0; + return out; +} + +#pragma pack(1) +class Matcher +{ +public: + Matcher() + { + valid = false; + isnumeric = false; + notequal = false; + usemin = false; + minequals = false; + usemax = false; + maxequals = false; + maxval = 0.0f; + minval = 0.0f; + + token = UTL_INVAL_SYMBOL; + rawtoken = UTL_INVAL_SYMBOL; + } + + void Describe( void ) + { + if ( !valid ) + { + DevMsg( " invalid!\n" ); + return; + } + char sz[ 128 ]; + + sz[ 0] = 0; + int minmaxcount = 0; + if ( usemin ) + { + Q_snprintf( sz, sizeof( sz ), ">%s%.3f", minequals ? "=" : "", minval ); + minmaxcount++; + } + if ( usemax ) + { + char sz2[ 128 ]; + Q_snprintf( sz2, sizeof( sz2 ), "<%s%.3f", maxequals ? "=" : "", maxval ); + + if ( minmaxcount > 0 ) + { + Q_strncat( sz, " and ", sizeof( sz ), COPY_ALL_CHARACTERS ); + } + Q_strncat( sz, sz2, sizeof( sz ), COPY_ALL_CHARACTERS ); + minmaxcount++; + } + + if ( minmaxcount >= 1 ) + { + DevMsg( " matcher: %s\n", sz ); + return; + } + + if ( notequal ) + { + DevMsg( " matcher: !=%s\n", GetToken() ); + return; + } + + DevMsg( " matcher: ==%s\n", GetToken() ); + } + + float maxval; + float minval; + + bool valid : 1; //1 + bool isnumeric : 1; //2 + bool notequal : 1; //3 + bool usemin : 1; //4 + bool minequals : 1; //5 + bool usemax : 1; //6 + bool maxequals : 1; //7 + + void SetToken( char const *s ) + { + token = g_RS.AddString( s ); + } + + char const *GetToken() + { + if ( token.IsValid() ) + { + return g_RS.String( token ); + } + return ""; + } + void SetRaw( char const *raw ) + { + rawtoken = g_RS.AddString( raw ); + } + char const *GetRaw() + { + if ( rawtoken.IsValid() ) + { + return g_RS.String( rawtoken ); + } + return ""; + } + +private: + CUtlSymbol token; + CUtlSymbol rawtoken; +}; + +struct Response +{ + DECLARE_SIMPLE_DATADESC(); + + Response() + { + type = RESPONSE_NONE; + value = NULL; + weight.SetFloat( 1.0f ); + depletioncount = 0; + first = false; + last = false; + } + + Response( const Response& src ) + { + weight = src.weight; + type = src.type; + value = CopyString( src.value ); + depletioncount = src.depletioncount; + first = src.first; + last = src.last; + } + + Response& operator =( const Response& src ) + { + if ( this == &src ) + return *this; + weight = src.weight; + type = src.type; + value = CopyString( src.value ); + depletioncount = src.depletioncount; + first = src.first; + last = src.last; + return *this; + } + + ~Response() + { + delete[] value; + } + + ResponseType_t GetType() { return (ResponseType_t)type; } + + char *value; // fixed up value spot // 4 + float16 weight; // 6 + + byte depletioncount; // 7 + byte type : 6; // 8 + byte first : 1; // + byte last : 1; // +}; + +struct ResponseGroup +{ + DECLARE_SIMPLE_DATADESC(); + + ResponseGroup() + { + // By default visit all nodes before repeating + m_bSequential = false; + m_bNoRepeat = false; + m_bEnabled = true; + m_nCurrentIndex = 0; + m_bDepleteBeforeRepeat = true; + m_nDepletionCount = 1; + m_bHasFirst = false; + m_bHasLast = false; + } + + ResponseGroup( const ResponseGroup& src ) + { + int c = src.group.Count(); + for ( int i = 0; i < c; i++ ) + { + group.AddToTail( src.group[ i ] ); + } + + rp = src.rp; + m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat; + m_nDepletionCount = src.m_nDepletionCount; + m_bHasFirst = src.m_bHasFirst; + m_bHasLast = src.m_bHasLast; + m_bSequential = src.m_bSequential; + m_bNoRepeat = src.m_bNoRepeat; + m_bEnabled = src.m_bEnabled; + m_nCurrentIndex = src.m_nCurrentIndex; + } + + ResponseGroup& operator=( const ResponseGroup& src ) + { + if ( this == &src ) + return *this; + int c = src.group.Count(); + for ( int i = 0; i < c; i++ ) + { + group.AddToTail( src.group[ i ] ); + } + + rp = src.rp; + m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat; + m_nDepletionCount = src.m_nDepletionCount; + m_bHasFirst = src.m_bHasFirst; + m_bHasLast = src.m_bHasLast; + m_bSequential = src.m_bSequential; + m_bNoRepeat = src.m_bNoRepeat; + m_bEnabled = src.m_bEnabled; + m_nCurrentIndex = src.m_nCurrentIndex; + return *this; + } + + bool HasUndepletedChoices() const + { + if ( !m_bDepleteBeforeRepeat ) + return true; + + int c = group.Count(); + for ( int i = 0; i < c; i++ ) + { + if ( group[ i ].depletioncount != m_nDepletionCount ) + return true; + } + + return false; + } + + void MarkResponseUsed( int idx ) + { + if ( !m_bDepleteBeforeRepeat ) + return; + + if ( idx < 0 || idx >= group.Count() ) + { + Assert( 0 ); + return; + } + + group[ idx ].depletioncount = m_nDepletionCount; + } + + void ResetDepletionCount() + { + if ( !m_bDepleteBeforeRepeat ) + return; + ++m_nDepletionCount; + } + + void Reset() + { + ResetDepletionCount(); + SetEnabled( true ); + SetCurrentIndex( 0 ); + m_nDepletionCount = 1; + + for ( int i = 0; i < group.Count(); ++i ) + { + group[ i ].depletioncount = 0; + } + } + + bool HasUndepletedFirst( int& index ) + { + index = -1; + + if ( !m_bDepleteBeforeRepeat ) + return false; + + int c = group.Count(); + for ( int i = 0; i < c; i++ ) + { + Response *r = &group[ i ]; + + if ( ( r->depletioncount != m_nDepletionCount ) && r->first ) + { + index = i; + return true; + } + } + + return false; + } + + bool HasUndepletedLast( int& index ) + { + index = -1; + + if ( !m_bDepleteBeforeRepeat ) + return false; + + int c = group.Count(); + for ( int i = 0; i < c; i++ ) + { + Response *r = &group[ i ]; + + if ( ( r->depletioncount != m_nDepletionCount ) && r->last ) + { + index = i; + return true; + } + } + + return false; + } + + bool ShouldCheckRepeats() const { return m_bDepleteBeforeRepeat; } + int GetDepletionCount() const { return m_nDepletionCount; } + + bool IsSequential() const { return m_bSequential; } + void SetSequential( bool seq ) { m_bSequential = seq; } + + bool IsNoRepeat() const { return m_bNoRepeat; } + void SetNoRepeat( bool norepeat ) { m_bNoRepeat = norepeat; } + + bool IsEnabled() const { return m_bEnabled; } + void SetEnabled( bool enabled ) { m_bEnabled = enabled; } + + int GetCurrentIndex() const { return m_nCurrentIndex; } + void SetCurrentIndex( byte idx ) { m_nCurrentIndex = idx; } + + CUtlVector< Response > group; + + AI_ResponseParams rp; + + bool m_bEnabled; + + byte m_nCurrentIndex; + // Invalidation counter + byte m_nDepletionCount; + + // Use all slots before repeating any + bool m_bDepleteBeforeRepeat : 1; + bool m_bHasFirst : 1; + bool m_bHasLast : 1; + bool m_bSequential : 1; + bool m_bNoRepeat : 1; + +}; + +struct Criteria +{ + Criteria() + { + name = NULL; + value = NULL; + weight.SetFloat( 1.0f ); + required = false; + } + Criteria& operator =(const Criteria& src ) + { + if ( this == &src ) + return *this; + + name = CopyString( src.name ); + value = CopyString( src.value ); + weight = src.weight; + required = src.required; + + matcher = src.matcher; + + int c = src.subcriteria.Count(); + for ( int i = 0; i < c; i++ ) + { + subcriteria.AddToTail( src.subcriteria[ i ] ); + } + + return *this; + } + Criteria(const Criteria& src ) + { + name = CopyString( src.name ); + value = CopyString( src.value ); + weight = src.weight; + required = src.required; + + matcher = src.matcher; + + int c = src.subcriteria.Count(); + for ( int i = 0; i < c; i++ ) + { + subcriteria.AddToTail( src.subcriteria[ i ] ); + } + } + ~Criteria() + { + delete[] name; + delete[] value; + } + + bool IsSubCriteriaType() const + { + return ( subcriteria.Count() > 0 ) ? true : false; + } + + char *name; + char *value; + float16 weight; + bool required; + + Matcher matcher; + + // Indices into sub criteria + CUtlVector< unsigned short > subcriteria; +}; + +struct Rule +{ + Rule() + { + m_bMatchOnce = false; + m_bEnabled = true; + m_szContext = NULL; + m_bApplyContextToWorld = false; + } + + Rule& operator =( const Rule& src ) + { + if ( this == &src ) + return *this; + + int i; + int c; + + c = src.m_Criteria.Count(); + for ( i = 0; i < c; i++ ) + { + m_Criteria.AddToTail( src.m_Criteria[ i ] ); + } + + c = src.m_Responses.Count(); + for ( i = 0; i < c; i++ ) + { + m_Responses.AddToTail( src.m_Responses[ i ] ); + } + + SetContext( src.m_szContext ); + m_bMatchOnce = src.m_bMatchOnce; + m_bEnabled = src.m_bEnabled; + m_bApplyContextToWorld = src.m_bApplyContextToWorld; + return *this; + } + + Rule( const Rule& src ) + { + int i; + int c; + + c = src.m_Criteria.Count(); + for ( i = 0; i < c; i++ ) + { + m_Criteria.AddToTail( src.m_Criteria[ i ] ); + } + + c = src.m_Responses.Count(); + for ( i = 0; i < c; i++ ) + { + m_Responses.AddToTail( src.m_Responses[ i ] ); + } + + SetContext( src.m_szContext ); + m_bMatchOnce = src.m_bMatchOnce; + m_bEnabled = src.m_bEnabled; + m_bApplyContextToWorld = src.m_bApplyContextToWorld; + } + + ~Rule() + { + delete[] m_szContext; + } + + void SetContext( const char *context ) + { + delete[] m_szContext; + m_szContext = CopyString( context ); + } + + const char *GetContext( void ) const { return m_szContext; } + + bool IsEnabled() const { return m_bEnabled; } + void Disable() { m_bEnabled = false; } + bool IsMatchOnce() const { return m_bMatchOnce; } + bool IsApplyContextToWorld() const { return m_bApplyContextToWorld; } + + // Indices into underlying criteria and response dictionaries + CUtlVector< unsigned short > m_Criteria; + CUtlVector< unsigned short> m_Responses; + + char *m_szContext; + bool m_bApplyContextToWorld : 1; + + bool m_bMatchOnce : 1; + bool m_bEnabled : 1; +}; +#pragma pack() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +abstract_class CResponseSystem : public IResponseSystem +{ +public: + CResponseSystem(); + ~CResponseSystem(); + + // IResponseSystem + virtual bool FindBestResponse( const AI_CriteriaSet& set, AI_Response& response, IResponseFilter *pFilter = NULL ); + virtual void GetAllResponses( CUtlVector *pResponses ); + + virtual void Release() = 0; + + virtual void DumpRules(); + + virtual void Precache(); + + virtual void PrecacheResponses( bool bEnable ) + { + m_bPrecache = bEnable; + } + + bool ShouldPrecache() { return m_bPrecache; } + bool IsCustomManagable() { return m_bCustomManagable; } + + void Clear(); + + void DumpDictionary( const char *pszName ); + +protected: + + virtual const char *GetScriptFile( void ) = 0; + void LoadRuleSet( const char *setname ); + + void ResetResponseGroups(); + + float LookForCriteria( const AI_CriteriaSet &criteriaSet, int iCriteria ); + float RecursiveLookForCriteria( const AI_CriteriaSet &criteriaSet, Criteria *pParent ); + +public: + + void CopyRuleFrom( Rule *pSrcRule, int iRule, CResponseSystem *pCustomSystem ); + void CopyCriteriaFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ); + void CopyResponsesFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ); + void CopyEnumerationsFrom( CResponseSystem *pCustomSystem ); + +//private: + + struct Enumeration + { + float value; + }; + + struct ResponseSearchResult + { + ResponseSearchResult() + { + group = NULL; + action = NULL; + } + + ResponseGroup *group; + Response *action; + }; + + inline bool ParseToken( void ) + { + if ( m_bUnget ) + { + m_bUnget = false; + return true; + } + if ( m_ScriptStack.Count() <= 0 ) + { + Assert( 0 ); + return false; + } + + m_ScriptStack[ 0 ].currenttoken = engine->ParseFile( m_ScriptStack[ 0 ].currenttoken, token, sizeof( token ) ); + m_ScriptStack[ 0 ].tokencount++; + return m_ScriptStack[ 0 ].currenttoken != NULL ? true : false; + } + + inline void Unget() + { + m_bUnget = true; + } + + inline bool TokenWaiting( void ) + { + if ( m_ScriptStack.Count() <= 0 ) + { + Assert( 0 ); + return false; + } + + const char *p = m_ScriptStack[ 0 ].currenttoken; + + if ( !p ) + { + Error( "AI_ResponseSystem: Unxpected TokenWaiting() with NULL buffer in %p", m_ScriptStack[ 0 ].name ); + return false; + } + + + while ( *p && *p!='\n') + { + // Special handler for // comment blocks + if ( *p == '/' && *(p+1) == '/' ) + return false; + + if ( !isspace( *p ) || isalnum( *p ) ) + return true; + + p++; + } + + return false; + } + + void ParseOneResponse( const char *responseGroupName, ResponseGroup& group ); + + void ParseInclude( CStringPool &includedFiles ); + void ParseResponse( void ); + void ParseCriterion( void ); + void ParseRule( void ); + void ParseEnumeration( void ); + + int ParseOneCriterion( const char *criterionName ); + + bool Compare( const char *setValue, Criteria *c, bool verbose = false ); + bool CompareUsingMatcher( const char *setValue, Matcher& m, bool verbose = false ); + void ComputeMatcher( Criteria *c, Matcher& matcher ); + void ResolveToken( Matcher& matcher, char *token, size_t bufsize, char const *rawtoken ); + float LookupEnumeration( const char *name, bool& found ); + + int FindBestMatchingRule( const AI_CriteriaSet& set, bool verbose ); + + float ScoreCriteriaAgainstRule( const AI_CriteriaSet& set, int irule, bool verbose = false ); + float RecursiveScoreSubcriteriaAgainstRule( const AI_CriteriaSet& set, Criteria *parent, bool& exclude, bool verbose /*=false*/ ); + float ScoreCriteriaAgainstRuleCriteria( const AI_CriteriaSet& set, int icriterion, bool& exclude, bool verbose = false ); + bool GetBestResponse( ResponseSearchResult& result, Rule *rule, bool verbose = false, IResponseFilter *pFilter = NULL ); + bool ResolveResponse( ResponseSearchResult& result, int depth, const char *name, bool verbose = false, IResponseFilter *pFilter = NULL ); + int SelectWeightedResponseFromResponseGroup( ResponseGroup *g, IResponseFilter *pFilter ); + void DescribeResponseGroup( ResponseGroup *group, int selected, int depth ); + void DebugPrint( int depth, const char *fmt, ... ); + + void LoadFromBuffer( const char *scriptfile, const char *buffer, CStringPool &includedFiles ); + + void GetCurrentScript( char *buf, size_t buflen ); + int GetCurrentToken() const; + void SetCurrentScript( const char *script ); + bool IsRootCommand(); + + void PushScript( const char *scriptfile, unsigned char *buffer ); + void PopScript(void); + + void ResponseWarning( const char *fmt, ... ); + + CUtlDict< ResponseGroup, short > m_Responses; + CUtlDict< Criteria, short > m_Criteria; + CUtlDict< Rule, short > m_Rules; + CUtlDict< Enumeration, short > m_Enumerations; + + char token[ 1204 ]; + + bool m_bUnget; + bool m_bPrecache; + + bool m_bCustomManagable; + + struct ScriptEntry + { + unsigned char *buffer; + FileNameHandle_t name; + const char *currenttoken; + int tokencount; + }; + + CUtlVector< ScriptEntry > m_ScriptStack; + + friend class CDefaultResponseSystemSaveRestoreBlockHandler; + friend class CResponseSystemSaveRestoreOps; +}; + +BEGIN_SIMPLE_DATADESC( Response ) + // DEFINE_FIELD( type, FIELD_INTEGER ), + // DEFINE_ARRAY( value, FIELD_CHARACTER ), + // DEFINE_FIELD( weight, FIELD_FLOAT ), + DEFINE_FIELD( depletioncount, FIELD_CHARACTER ), + // DEFINE_FIELD( first, FIELD_BOOLEAN ), + // DEFINE_FIELD( last, FIELD_BOOLEAN ), +END_DATADESC() + +BEGIN_SIMPLE_DATADESC( ResponseGroup ) + // DEFINE_FIELD( group, FIELD_UTLVECTOR ), + // DEFINE_FIELD( rp, FIELD_EMBEDDED ), + // DEFINE_FIELD( m_bDepleteBeforeRepeat, FIELD_BOOLEAN ), + DEFINE_FIELD( m_nDepletionCount, FIELD_CHARACTER ), + // DEFINE_FIELD( m_bHasFirst, FIELD_BOOLEAN ), + // DEFINE_FIELD( m_bHasLast, FIELD_BOOLEAN ), + // DEFINE_FIELD( m_bSequential, FIELD_BOOLEAN ), + // DEFINE_FIELD( m_bNoRepeat, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), + DEFINE_FIELD( m_nCurrentIndex, FIELD_CHARACTER ), +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CResponseSystem::CResponseSystem() +{ + token[0] = 0; + m_bUnget = false; + m_bPrecache = true; + m_bCustomManagable = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CResponseSystem::~CResponseSystem() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : char const +//----------------------------------------------------------------------------- +void CResponseSystem::GetCurrentScript( char *buf, size_t buflen ) +{ + Assert( buf ); + buf[ 0 ] = 0; + if ( m_ScriptStack.Count() <= 0 ) + return; + + if ( filesystem->String( m_ScriptStack[ 0 ].name, buf, buflen ) ) + { + return; + } + buf[ 0 ] = 0; +} + +void CResponseSystem::PushScript( const char *scriptfile, unsigned char *buffer ) +{ + ScriptEntry e; + e.name = filesystem->FindOrAddFileName( scriptfile ); + e.buffer = buffer; + e.currenttoken = (char *)e.buffer; + e.tokencount = 0; + m_ScriptStack.AddToHead( e ); +} + +void CResponseSystem::PopScript(void) +{ + Assert( m_ScriptStack.Count() >= 1 ); + if ( m_ScriptStack.Count() <= 0 ) + return; + + m_ScriptStack.Remove( 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::Clear() +{ + m_Responses.RemoveAll(); + m_Criteria.RemoveAll(); + m_Rules.RemoveAll(); + m_Enumerations.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// found - +// Output : float +//----------------------------------------------------------------------------- +float CResponseSystem::LookupEnumeration( const char *name, bool& found ) +{ + int idx = m_Enumerations.Find( name ); + if ( idx == m_Enumerations.InvalidIndex() ) + { + found = false; + return 0.0f; + } + + + found = true; + return m_Enumerations[ idx ].value; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : matcher - +//----------------------------------------------------------------------------- +void CResponseSystem::ResolveToken( Matcher& matcher, char *token, size_t bufsize, char const *rawtoken ) +{ + if ( rawtoken[0] != '[' ) + { + Q_strncpy( token, rawtoken, bufsize ); + return; + } + + // Now lookup enumeration + bool found = false; + float f = LookupEnumeration( rawtoken, found ); + if ( !found ) + { + Q_strncpy( token, rawtoken, bufsize ); + ResponseWarning( "No such enumeration '%s'\n", token ); + return; + } + + Q_snprintf( token, bufsize, "%f", f ); +} + + +static bool AppearsToBeANumber( char const *token ) +{ + if ( atof( token ) != 0.0f ) + return true; + + char const *p = token; + while ( *p ) + { + if ( *p != '0' ) + return false; + + p++; + } + + return true; +} + +void CResponseSystem::ComputeMatcher( Criteria *c, Matcher& matcher ) +{ + const char *s = c->value; + if ( !s ) + { + matcher.valid = false; + return; + } + + const char *in = s; + + char token[ 128 ]; + char rawtoken[ 128 ]; + + token[ 0 ] = 0; + rawtoken[ 0 ] = 0; + + int n = 0; + + bool gt = false; + bool lt = false; + bool eq = false; + bool nt = false; + + bool done = false; + while ( !done ) + { + switch( *in ) + { + case '>': + { + gt = true; + Assert( !lt ); // Can't be both + } + break; + case '<': + { + lt = true; + Assert( !gt ); // Can't be both + } + break; + case '=': + { + eq = true; + } + break; + case ',': + case '\0': + { + rawtoken[ n ] = 0; + n = 0; + + // Convert raw token to real token in case token is an enumerated type specifier + ResolveToken( matcher, token, sizeof( token ), rawtoken ); + + // Fill in first data set + if ( gt ) + { + matcher.usemin = true; + matcher.minequals = eq; + matcher.minval = (float)atof( token ); + + matcher.isnumeric = true; + } + else if ( lt ) + { + matcher.usemax = true; + matcher.maxequals = eq; + matcher.maxval = (float)atof( token ); + + matcher.isnumeric = true; + } + else + { + if ( *in == ',' ) + { + // If there's a comma, this better have been a less than or a gt key + Assert( 0 ); + } + + matcher.notequal = nt; + + matcher.isnumeric = AppearsToBeANumber( token ); + } + + gt = lt = eq = nt = false; + + if ( !(*in) ) + { + done = true; + } + } + break; + case '!': + nt = true; + break; + default: + rawtoken[ n++ ] = *in; + break; + } + + in++; + } + + matcher.SetToken( token ); + matcher.SetRaw( rawtoken ); + matcher.valid = true; +} + +bool CResponseSystem::CompareUsingMatcher( const char *setValue, Matcher& m, bool verbose /*=false*/ ) +{ + if ( !m.valid ) + return false; + + float v = (float)atof( setValue ); + if ( setValue[0] == '[' ) + { + bool found = false; + v = LookupEnumeration( setValue, found ); + } + + int minmaxcount = 0; + + if ( m.usemin ) + { + if ( m.minequals ) + { + if ( v < m.minval ) + return false; + } + else + { + if ( v <= m.minval ) + return false; + } + + ++minmaxcount; + } + + if ( m.usemax ) + { + if ( m.maxequals ) + { + if ( v > m.maxval ) + return false; + } + else + { + if ( v >= m.maxval ) + return false; + } + + ++minmaxcount; + } + + // Had one or both criteria and met them + if ( minmaxcount >= 1 ) + { + return true; + } + + if ( m.notequal ) + { + if ( m.isnumeric ) + { + if ( v == (float)atof( m.GetToken() ) ) + return false; + } + else + { + if ( !Q_stricmp( setValue, m.GetToken() ) ) + return false; + } + + return true; + } + + if ( m.isnumeric ) + { + // If the setValue is "", the NPC doesn't have the key at all, + // in which case we shouldn't match "0". + if ( !setValue || !setValue[0] ) + return false; + + return v == (float)atof( m.GetToken() ); + } + + return !Q_stricmp( setValue, m.GetToken() ) ? true : false; +} + +bool CResponseSystem::Compare( const char *setValue, Criteria *c, bool verbose /*= false*/ ) +{ + Assert( c ); + Assert( setValue ); + + bool bret = CompareUsingMatcher( setValue, c->matcher, verbose ); + + if ( verbose ) + { + DevMsg( "'%20s' vs. '%20s' = ", setValue, c->value ); + + { + //DevMsg( "\n" ); + //m.Describe(); + } + } + return bret; +} + +float CResponseSystem::RecursiveScoreSubcriteriaAgainstRule( const AI_CriteriaSet& set, Criteria *parent, bool& exclude, bool verbose /*=false*/ ) +{ + float score = 0.0f; + int subcount = parent->subcriteria.Count(); + for ( int i = 0; i < subcount; i++ ) + { + int icriterion = parent->subcriteria[ i ]; + + bool excludesubrule = false; + if (verbose) + { + DevMsg( "\n" ); + } + score += ScoreCriteriaAgainstRuleCriteria( set, icriterion, excludesubrule, verbose ); + } + + exclude = ( parent->required && score == 0.0f ) ? true : false; + + return score * parent->weight.GetFloat(); +} + +float CResponseSystem::RecursiveLookForCriteria( const AI_CriteriaSet &criteriaSet, Criteria *pParent ) +{ + float flScore = 0.0f; + int nSubCount = pParent->subcriteria.Count(); + for ( int iSub = 0; iSub < nSubCount; ++iSub ) + { + int iCriteria = pParent->subcriteria[iSub]; + flScore += LookForCriteria( criteriaSet, iCriteria ); + } + + return flScore; +} + +float CResponseSystem::LookForCriteria( const AI_CriteriaSet &criteriaSet, int iCriteria ) +{ + Criteria *pCriteria = &m_Criteria[iCriteria]; + if ( pCriteria->IsSubCriteriaType() ) + { + return RecursiveLookForCriteria( criteriaSet, pCriteria ); + } + + int iIndex = criteriaSet.FindCriterionIndex( pCriteria->name ); + if ( iIndex == -1 ) + return 0.0f; + + Assert( criteriaSet.GetValue( iIndex ) ); + if ( Q_stricmp( criteriaSet.GetValue( iIndex ), pCriteria->value ) ) + return 0.0f; + + return 1.0f; +} + +float CResponseSystem::ScoreCriteriaAgainstRuleCriteria( const AI_CriteriaSet& set, int icriterion, bool& exclude, bool verbose /*=false*/ ) +{ + Criteria *c = &m_Criteria[ icriterion ]; + + if ( c->IsSubCriteriaType() ) + { + return RecursiveScoreSubcriteriaAgainstRule( set, c, exclude, verbose ); + } + + if ( verbose ) + { + DevMsg( " criterion '%25s':'%15s' ", m_Criteria.GetElementName( icriterion ), c->name ); + } + + exclude = false; + + float score = 0.0f; + + const char *actualValue = ""; + + int found = set.FindCriterionIndex( c->name ); + if ( found != -1 ) + { + actualValue = set.GetValue( found ); + if ( !actualValue ) + { + Assert( 0 ); + return score; + } + } + + Assert( actualValue ); + + if ( Compare( actualValue, c, verbose ) ) + { + float w = set.GetWeight( found ); + score = w * c->weight.GetFloat(); + + if ( verbose ) + { + DevMsg( "matched, weight %4.2f (s %4.2f x c %4.2f)", + score, w, c->weight.GetFloat() ); + } + } + else + { + if ( c->required ) + { + exclude = true; + if ( verbose ) + { + DevMsg( "failed (+exclude rule)" ); + } + } + else + { + if ( verbose ) + { + DevMsg( "failed" ); + } + } + } + + return score; +} + +float CResponseSystem::ScoreCriteriaAgainstRule( const AI_CriteriaSet& set, int irule, bool verbose /*=false*/ ) +{ + Rule *rule = &m_Rules[ irule ]; + float score = 0.0f; + + bool bBeingWatched = false; + + // See if we're trying to debug this rule + const char *pszText = rr_debugrule.GetString(); + if ( pszText && pszText[0] && !Q_stricmp( pszText, m_Rules.GetElementName( irule ) ) ) + { + bBeingWatched = true; + } + + if ( !rule->IsEnabled() ) + { + if ( bBeingWatched ) + { + DevMsg("Rule '%s' is disabled.\n", m_Rules.GetElementName( irule ) ); + } + return 0.0f; + } + + if ( bBeingWatched ) + { + verbose = true; + } + + if ( verbose ) + { + DevMsg( "Scoring rule '%s' (%i)\n{\n", m_Rules.GetElementName( irule ), irule+1 ); + } + + // Iterate set criteria + int count = rule->m_Criteria.Count(); + int i; + for ( i = 0; i < count; i++ ) + { + int icriterion = rule->m_Criteria[ i ]; + + bool exclude = false; + score += ScoreCriteriaAgainstRuleCriteria( set, icriterion, exclude, verbose ); + + if ( verbose ) + { + DevMsg( ", score %4.2f\n", score ); + } + + if ( exclude ) + { + score = 0.0f; + break; + } + } + + if ( verbose ) + { + DevMsg( "}\n" ); + } + + return score; +} + +void CResponseSystem::DebugPrint( int depth, const char *fmt, ... ) +{ + int indentchars = 3 * depth; + char *indent = (char *)_alloca( indentchars + 1); + indent[ indentchars ] = 0; + while ( --indentchars >= 0 ) + { + indent[ indentchars ] = ' '; + } + + // Dump text to debugging console. + va_list argptr; + char szText[1024]; + + va_start (argptr, fmt); + Q_vsnprintf (szText, sizeof( szText ), fmt, argptr); + va_end (argptr); + + DevMsg( "%s%s", indent, szText ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::ResetResponseGroups() +{ + int i; + int c = m_Responses.Count(); + for ( i = 0; i < c; i++ ) + { + m_Responses[ i ].Reset(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *g - +// Output : int +//----------------------------------------------------------------------------- +int CResponseSystem::SelectWeightedResponseFromResponseGroup( ResponseGroup *g, IResponseFilter *pFilter ) +{ + int c = g->group.Count(); + if ( !c ) + { + Assert( !"Expecting response group with >= 1 elements" ); + return -1; + } + + int i; + + // Fake depletion of unavailable choices + CUtlVector fakedDepletes; + if ( pFilter && g->ShouldCheckRepeats() ) + { + for ( i = 0; i < c; i++ ) + { + Response *r = &g->group[ i ]; + if ( r->depletioncount != g->GetDepletionCount() && !pFilter->IsValidResponse( r->GetType(), r->value ) ) + { + fakedDepletes.AddToTail( i ); + g->MarkResponseUsed( i ); + } + } + } + + if ( !g->HasUndepletedChoices() ) + { + g->ResetDepletionCount(); + + if ( pFilter && g->ShouldCheckRepeats() ) + { + fakedDepletes.RemoveAll(); + for ( i = 0; i < c; i++ ) + { + Response *r = &g->group[ i ]; + if ( !pFilter->IsValidResponse( r->GetType(), r->value ) ) + { + fakedDepletes.AddToTail( i ); + g->MarkResponseUsed( i ); + } + } + } + + if ( !g->HasUndepletedChoices() ) + return -1; + + // Disable the group if we looped through all the way + if ( g->IsNoRepeat() ) + { + g->SetEnabled( false ); + return -1; + } + } + + bool checkrepeats = g->ShouldCheckRepeats(); + int depletioncount = g->GetDepletionCount(); + + float totalweight = 0.0f; + int slot = -1; + + if ( checkrepeats ) + { + int check= -1; + // Snag the first slot right away + if ( g->HasUndepletedFirst( check ) && check != -1 ) + { + slot = check; + } + + if ( slot == -1 && g->HasUndepletedLast( check ) && check != -1 ) + { + // If this is the only undepleted one, use it now + for ( i = 0; i < c; i++ ) + { + Response *r = &g->group[ i ]; + if ( checkrepeats && + ( r->depletioncount == depletioncount ) ) + { + continue; + } + + if ( r->last ) + { + Assert( i == check ); + continue; + } + + // There's still another undepleted entry + break; + } + + // No more undepleted so use the r->last slot + if ( i >= c ) + { + slot = check; + } + } + } + + if ( slot == -1 ) + { + for ( i = 0; i < c; i++ ) + { + Response *r = &g->group[ i ]; + if ( checkrepeats && + ( r->depletioncount == depletioncount ) ) + { + continue; + } + + // Always skip last entry here since we will deal with it above + if ( checkrepeats && r->last ) + continue; + + int prevSlot = slot; + + if ( !totalweight ) + { + slot = i; + } + + // Always assume very first slot will match + totalweight += r->weight.GetFloat(); + if ( !totalweight || random->RandomFloat(0,totalweight) < r->weight.GetFloat() ) + { + slot = i; + } + + if ( !checkrepeats && slot != prevSlot && pFilter && !pFilter->IsValidResponse( r->GetType(), r->value ) ) + { + slot = prevSlot; + totalweight -= r->weight.GetFloat(); + } + } + } + + if ( slot != -1 ) + g->MarkResponseUsed( slot ); + + // Revert fake depletion of unavailable choices + if ( pFilter && g->ShouldCheckRepeats() ) + { + for ( i = 0; i < fakedDepletes.Count(); i++ ) + { + g->group[ fakedDepletes[ i ] ].depletioncount = 0;; + } + } + + return slot; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : searchResult - +// depth - +// *name - +// verbose - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CResponseSystem::ResolveResponse( ResponseSearchResult& searchResult, int depth, const char *name, bool verbose /*= false*/, IResponseFilter *pFilter ) +{ + int responseIndex = m_Responses.Find( name ); + if ( responseIndex == m_Responses.InvalidIndex() ) + return false; + + ResponseGroup *g = &m_Responses[ responseIndex ]; + // Group has been disabled + if ( !g->IsEnabled() ) + return false; + + int c = g->group.Count(); + if ( !c ) + return false; + + int idx = 0; + + if ( g->IsSequential() ) + { + // See if next index is valid + int initialIndex = g->GetCurrentIndex(); + bool bFoundValid = false; + + do + { + idx = g->GetCurrentIndex(); + g->SetCurrentIndex( idx + 1 ); + if ( idx >= c ) + { + if ( g->IsNoRepeat() ) + { + g->SetEnabled( false ); + return false; + } + idx = 0; + g->SetCurrentIndex( 0 ); + } + + if ( !pFilter || pFilter->IsValidResponse( g->group[idx].GetType(), g->group[idx].value ) ) + { + bFoundValid = true; + break; + } + + } while ( g->GetCurrentIndex() != initialIndex ); + + if ( !bFoundValid ) + return false; + } + else + { + idx = SelectWeightedResponseFromResponseGroup( g, pFilter ); + if ( idx < 0 ) + return false; + } + + if ( verbose ) + { + DebugPrint( depth, "%s\n", m_Responses.GetElementName( responseIndex ) ); + DebugPrint( depth, "{\n" ); + DescribeResponseGroup( g, idx, depth ); + } + + bool bret = true; + + Response *result = &g->group[ idx ]; + if ( result->type == RESPONSE_RESPONSE ) + { + // Recurse + bret = ResolveResponse( searchResult, depth + 1, result->value, verbose, pFilter ); + } + else + { + searchResult.action = result; + searchResult.group = g; + } + + if( verbose ) + { + DebugPrint( depth, "}\n" ); + } + + return bret; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *group - +// selected - +// depth - +//----------------------------------------------------------------------------- +void CResponseSystem::DescribeResponseGroup( ResponseGroup *group, int selected, int depth ) +{ + int c = group->group.Count(); + + for ( int i = 0; i < c ; i++ ) + { + Response *r = &group->group[ i ]; + DebugPrint( depth + 1, "%s%20s : %40s %5.3f\n", + i == selected ? "-> " : " ", + AI_Response::DescribeResponse( r->GetType() ), + r->value, + r->weight.GetFloat() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *rule - +// Output : CResponseSystem::Response +//----------------------------------------------------------------------------- +bool CResponseSystem::GetBestResponse( ResponseSearchResult& searchResult, Rule *rule, bool verbose /*=false*/, IResponseFilter *pFilter ) +{ + int c = rule->m_Responses.Count(); + if ( !c ) + return false; + + int index = random->RandomInt( 0, c - 1 ); + int groupIndex = rule->m_Responses[ index ]; + + ResponseGroup *g = &m_Responses[ groupIndex ]; + + // Group has been disabled + if ( !g->IsEnabled() ) + return false; + + int count = g->group.Count(); + if ( !count ) + return false; + + int responseIndex = 0; + + if ( g->IsSequential() ) + { + // See if next index is valid + int initialIndex = g->GetCurrentIndex(); + bool bFoundValid = false; + + do + { + responseIndex = g->GetCurrentIndex(); + g->SetCurrentIndex( responseIndex + 1 ); + if ( responseIndex >= count ) + { + if ( g->IsNoRepeat() ) + { + g->SetEnabled( false ); + return false; + } + responseIndex = 0; + g->SetCurrentIndex( 0 ); + } + + if ( !pFilter || pFilter->IsValidResponse( g->group[responseIndex].GetType(), g->group[responseIndex].value ) ) + { + bFoundValid = true; + break; + } + + } while ( g->GetCurrentIndex() != initialIndex ); + + if ( !bFoundValid ) + return false; + } + else + { + responseIndex = SelectWeightedResponseFromResponseGroup( g, pFilter ); + if ( responseIndex < 0 ) + return false; + } + + + Response *r = &g->group[ responseIndex ]; + + int depth = 0; + + if ( verbose ) + { + DebugPrint( depth, "%s\n", m_Responses.GetElementName( groupIndex ) ); + DebugPrint( depth, "{\n" ); + + DescribeResponseGroup( g, responseIndex, depth ); + } + + bool bret = true; + + if ( r->type == RESPONSE_RESPONSE ) + { + bret = ResolveResponse( searchResult, depth + 1, r->value, verbose, pFilter ); + } + else + { + searchResult.action = r; + searchResult.group = g; + } + + if ( verbose ) + { + DebugPrint( depth, "}\n" ); + } + + return bret; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : set - +// verbose - +// Output : int +//----------------------------------------------------------------------------- +int CResponseSystem::FindBestMatchingRule( const AI_CriteriaSet& set, bool verbose ) +{ + CUtlVector< int > bestrules; + float bestscore = 0.001f; + + int c = m_Rules.Count(); + int i; + for ( i = 0; i < c; i++ ) + { + float score = ScoreCriteriaAgainstRule( set, i, verbose ); + // Check equals so that we keep track of all matching rules + if ( score >= bestscore ) + { + // Reset bucket + if( score != bestscore ) + { + bestscore = score; + bestrules.RemoveAll(); + } + + // Add to bucket + bestrules.AddToTail( i ); + } + } + + int bestCount = bestrules.Count(); + if ( bestCount <= 0 ) + return -1; + + if ( bestCount == 1 ) + return bestrules[ 0 ]; + + // Randomly pick one of the tied matching rules + int idx = random->RandomInt( 0, bestCount - 1 ); + if ( verbose ) + { + DevMsg( "Found %i matching rules, selecting slot %i\n", bestCount, idx ); + } + return bestrules[ idx ]; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : set - +// Output : AI_Response +//----------------------------------------------------------------------------- +bool CResponseSystem::FindBestResponse( const AI_CriteriaSet& set, AI_Response& response, IResponseFilter *pFilter ) +{ + bool valid = false; + + int iDbgResponse = rr_debugresponses.GetInt(); + bool showRules = ( iDbgResponse == 2 ); + bool showResult = ( iDbgResponse == 1 || iDbgResponse == 2 ); + + // Look for match. verbose mode used to be at level 2, but disabled because the writers don't actually care for that info. + int bestRule = FindBestMatchingRule( set, iDbgResponse == 3 ); + + ResponseType_t responseType = RESPONSE_NONE; + AI_ResponseParams rp; + + char ruleName[ 128 ]; + char responseName[ 128 ]; + const char *context; + bool bcontexttoworld; + ruleName[ 0 ] = 0; + responseName[ 0 ] = 0; + context = NULL; + bcontexttoworld = false; + if ( bestRule != -1 ) + { + Rule *r = &m_Rules[ bestRule ]; + + ResponseSearchResult result; + if ( GetBestResponse( result, r, showResult, pFilter ) ) + { + Q_strncpy( responseName, result.action->value, sizeof( responseName ) ); + responseType = result.action->GetType(); + rp = result.group->rp; + } + + Q_strncpy( ruleName, m_Rules.GetElementName( bestRule ), sizeof( ruleName ) ); + + // Disable the rule if it only allows for matching one time + if ( r->IsMatchOnce() ) + { + r->Disable(); + } + context = r->GetContext(); + bcontexttoworld = r->IsApplyContextToWorld(); + + valid = true; + } + + response.Init( responseType, responseName, set, rp, ruleName, context, bcontexttoworld ); + + if ( showResult ) + { + /* + // clipped -- chet doesn't really want this info + if ( valid ) + { + // Rescore the winner and dump to console + ScoreCriteriaAgainstRule( set, bestRule, true ); + } + */ + + + if ( valid || showRules ) + { + // Describe the response, too + response.Describe(); + } + } + + return valid; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CResponseSystem::GetAllResponses( CUtlVector *pResponses ) +{ + for ( int i = 0; i < (int)m_Responses.Count(); i++ ) + { + ResponseGroup &group = m_Responses[i]; + + for ( int j = 0; j < group.group.Count(); j++) + { + Response &response = group.group[j]; + if ( response.type != RESPONSE_RESPONSE ) + { + AI_Response *pResponse = new AI_Response; + pResponse->Init( response.GetType(), response.value, AI_CriteriaSet(), group.rp, NULL, NULL, false ); + pResponses->AddToTail(pResponse); + } + } + } +} + +static void TouchFile( char const *pchFileName ) +{ + filesystem->Size( pchFileName ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::Precache() +{ + bool bTouchFiles = CommandLine()->FindParm( "-makereslists" ) != 0; + + // enumerate and mark all the scripts so we know they're referenced + for ( int i = 0; i < (int)m_Responses.Count(); i++ ) + { + ResponseGroup &group = m_Responses[i]; + + for ( int j = 0; j < group.group.Count(); j++) + { + Response &response = group.group[j]; + + switch ( response.type ) + { + default: + break; + case RESPONSE_SCENE: + { + // fixup $gender references + char file[_MAX_PATH]; + Q_strncpy( file, response.value, sizeof(file) ); + char *gender = strstr( file, "$gender" ); + if ( gender ) + { + // replace with male & female + const char *postGender = gender + strlen("$gender"); + *gender = 0; + char genderFile[_MAX_PATH]; + // male + Q_snprintf( genderFile, sizeof(genderFile), "%smale%s", file, postGender); + + PrecacheInstancedScene( genderFile ); + if ( bTouchFiles ) + { + TouchFile( genderFile ); + } + + Q_snprintf( genderFile, sizeof(genderFile), "%sfemale%s", file, postGender); + + PrecacheInstancedScene( genderFile ); + if ( bTouchFiles ) + { + TouchFile( genderFile ); + } + } + else + { + PrecacheInstancedScene( file ); + if ( bTouchFiles ) + { + TouchFile( file ); + } + } + } + break; + case RESPONSE_SPEAK: + { + CBaseEntity::PrecacheScriptSound( response.value ); + } + break; + } + } + } +} + +void CResponseSystem::ParseInclude( CStringPool &includedFiles ) +{ + char includefile[ 256 ]; + ParseToken(); + Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token ); + + // check if the file is already included + if ( includedFiles.Find( includefile ) != NULL ) + { + return; + } + + MEM_ALLOC_CREDIT(); + + // Try and load it + CUtlBuffer buf; + if ( !filesystem->ReadFile( includefile, "GAME", buf ) ) + { + DevMsg( "Unable to load #included script %s\n", includefile ); + return; + } + + LoadFromBuffer( includefile, (const char *)buf.PeekGet(), includedFiles ); +} + +void CResponseSystem::LoadFromBuffer( const char *scriptfile, const char *buffer, CStringPool &includedFiles ) +{ + includedFiles.Allocate( scriptfile ); + PushScript( scriptfile, (unsigned char * )buffer ); + + if( rr_dumpresponses.GetBool() ) + { + DevMsg("Reading: %s\n", scriptfile ); + } + + while ( 1 ) + { + ParseToken(); + if ( !token[0] ) + { + break; + } + + if ( !Q_stricmp( token, "#include" ) ) + { + ParseInclude( includedFiles ); + } + else if ( !Q_stricmp( token, "response" ) ) + { + ParseResponse(); + } + else if ( !Q_stricmp( token, "criterion" ) || + !Q_stricmp( token, "criteria" ) ) + { + ParseCriterion(); + } + else if ( !Q_stricmp( token, "rule" ) ) + { + ParseRule(); + } + else if ( !Q_stricmp( token, "enumeration" ) ) + { + ParseEnumeration(); + } + else + { + int byteoffset = m_ScriptStack[ 0 ].currenttoken - (const char *)m_ScriptStack[ 0 ].buffer; + + Error( "CResponseSystem::LoadFromBuffer: Unknown entry type '%s', expecting 'response', 'criterion', 'enumeration' or 'rules' in file %s(offset:%i)\n", + token, scriptfile, byteoffset ); + break; + } + } + + if ( m_ScriptStack.Count() == 1 ) + { + char cur[ 256 ]; + GetCurrentScript( cur, sizeof( cur ) ); + DevMsg( 1, "CResponseSystem: %s (%i rules, %i criteria, and %i responses)\n", + cur, m_Rules.Count(), m_Criteria.Count(), m_Responses.Count() ); + + if( rr_dumpresponses.GetBool() ) + { + DumpRules(); + } + } + + PopScript(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::LoadRuleSet( const char *basescript ) +{ + int length = 0; + unsigned char *buffer = (unsigned char *)UTIL_LoadFileForMe( basescript, &length ); + if ( length <= 0 || !buffer ) + { + DevMsg( 1, "CResponseSystem: failed to load %s\n", basescript ); + return; + } + + CStringPool includedFiles; + + LoadFromBuffer( basescript, (const char *)buffer, includedFiles ); + + UTIL_FreeFile( buffer ); + + Assert( m_ScriptStack.Count() == 0 ); +} + +static ResponseType_t ComputeResponseType( const char *s ) +{ + if ( !Q_stricmp( s, "scene" ) ) + { + return RESPONSE_SCENE; + } + else if ( !Q_stricmp( s, "sentence" ) ) + { + return RESPONSE_SENTENCE; + } + else if ( !Q_stricmp( s, "speak" ) ) + { + return RESPONSE_SPEAK; + } + else if ( !Q_stricmp( s, "response" ) ) + { + return RESPONSE_RESPONSE; + } + else if ( !Q_stricmp( s, "print" ) ) + { + return RESPONSE_PRINT; + } + + return RESPONSE_NONE; +} + +void CResponseSystem::ParseOneResponse( const char *responseGroupName, ResponseGroup& group ) +{ + Response newResponse; + newResponse.weight.SetFloat( 1.0f ); + AI_ResponseParams *rp = &group.rp; + + newResponse.type = ComputeResponseType( token ); + if ( RESPONSE_NONE == newResponse.type ) + { + ResponseWarning( "response entry '%s' with unknown response type '%s'\n", responseGroupName, token ); + return; + } + + ParseToken(); + newResponse.value = CopyString( token ); + + while ( TokenWaiting() ) + { + ParseToken(); + if ( !Q_stricmp( token, "weight" ) ) + { + ParseToken(); + newResponse.weight.SetFloat( (float)atof( token ) ); + continue; + } + + if ( !Q_stricmp( token, "predelay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYBEFORESPEAK; + rp->predelay.FromInterval( ReadInterval( token ) ); + continue; + } + + if ( !Q_stricmp( token, "nodelay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.start = 0; + rp->delay.range = 0; + continue; + } + + if ( !Q_stricmp( token, "defaultdelay" ) ) + { + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.start = AIS_DEF_MIN_DELAY; + rp->delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY ); + continue; + } + + if ( !Q_stricmp( token, "delay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.FromInterval( ReadInterval( token ) ); + continue; + } + + if ( !Q_stricmp( token, "speakonce" ) ) + { + rp->flags |= AI_ResponseParams::RG_SPEAKONCE; + continue; + } + + if ( !Q_stricmp( token, "noscene" ) ) + { + rp->flags |= AI_ResponseParams::RG_DONT_USE_SCENE; + continue; + } + + if ( !Q_stricmp( token, "stop_on_nonidle" ) ) + { + rp->flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE; + continue; + } + + if ( !Q_stricmp( token, "odds" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_ODDS; + rp->odds = clamp( atoi( token ), 0, 100 ); + continue; + } + + if ( !Q_stricmp( token, "respeakdelay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_RESPEAKDELAY; + rp->respeakdelay.FromInterval( ReadInterval( token ) ); + continue; + } + + if ( !Q_stricmp( token, "weapondelay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_WEAPONDELAY; + rp->weapondelay.FromInterval( ReadInterval( token ) ); + continue; + } + + if ( !Q_stricmp( token, "soundlevel" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_SOUNDLEVEL; + rp->soundlevel = (soundlevel_t)TextToSoundLevel( token ); + continue; + } + + if ( !Q_stricmp( token, "displayfirst" ) ) + { + newResponse.first = true; + group.m_bHasFirst = true; + continue; + } + + if ( !Q_stricmp( token, "displaylast" ) ) + { + newResponse.last = true; + group.m_bHasLast= true; + continue; + } + + ResponseWarning( "response entry '%s' with unknown command '%s'\n", responseGroupName, token ); + } + + group.group.AddToTail( newResponse ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CResponseSystem::IsRootCommand() +{ + if ( !Q_stricmp( token, "#include" ) ) + return true; + if ( !Q_stricmp( token, "response" ) ) + return true; + if ( !Q_stricmp( token, "enumeration" ) ) + return true; + if ( !Q_stricmp( token, "criteria" ) ) + return true; + if ( !Q_stricmp( token, "criterion" ) ) + return true; + if ( !Q_stricmp( token, "rule" ) ) + return true; + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *kv - +//----------------------------------------------------------------------------- +void CResponseSystem::ParseResponse( void ) +{ + // Should have groupname at start + char responseGroupName[ 128 ]; + + ResponseGroup newGroup; + AI_ResponseParams *rp = &newGroup.rp; + + // Response Group Name + ParseToken(); + Q_strncpy( responseGroupName, token, sizeof( responseGroupName ) ); + + while ( 1 ) + { + ParseToken(); + + // Oops, part of next definition + if( IsRootCommand() ) + { + Unget(); + break; + } + + if ( !Q_stricmp( token, "{" ) ) + { + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + break; + + if ( !Q_stricmp( token, "permitrepeats" ) ) + { + newGroup.m_bDepleteBeforeRepeat = false; + continue; + } + else if ( !Q_stricmp( token, "sequential" ) ) + { + newGroup.SetSequential( true ); + continue; + } + else if ( !Q_stricmp( token, "norepeat" ) ) + { + newGroup.SetNoRepeat( true ); + continue; + } + + ParseOneResponse( responseGroupName, newGroup ); + } + break; + } + + if ( !Q_stricmp( token, "predelay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYBEFORESPEAK; + rp->predelay.FromInterval( ReadInterval( token ) ); + continue; + } + + if ( !Q_stricmp( token, "nodelay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.start = 0; + rp->delay.range = 0; + continue; + } + + if ( !Q_stricmp( token, "defaultdelay" ) ) + { + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.start = AIS_DEF_MIN_DELAY; + rp->delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY ); + continue; + } + + if ( !Q_stricmp( token, "delay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK; + rp->delay.FromInterval( ReadInterval( token ) ); + continue; + } + + if ( !Q_stricmp( token, "speakonce" ) ) + { + rp->flags |= AI_ResponseParams::RG_SPEAKONCE; + continue; + } + + if ( !Q_stricmp( token, "noscene" ) ) + { + rp->flags |= AI_ResponseParams::RG_DONT_USE_SCENE; + continue; + } + + if ( !Q_stricmp( token, "stop_on_nonidle" ) ) + { + rp->flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE; + continue; + } + + if ( !Q_stricmp( token, "odds" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_ODDS; + rp->odds = clamp( atoi( token ), 0, 100 ); + continue; + } + + if ( !Q_stricmp( token, "respeakdelay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_RESPEAKDELAY; + rp->respeakdelay.FromInterval( ReadInterval( token ) ); + continue; + } + + if ( !Q_stricmp( token, "weapondelay" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_WEAPONDELAY; + rp->weapondelay.FromInterval( ReadInterval( token ) ); + continue; + } + + if ( !Q_stricmp( token, "soundlevel" ) ) + { + ParseToken(); + rp->flags |= AI_ResponseParams::RG_SOUNDLEVEL; + rp->soundlevel = (soundlevel_t)TextToSoundLevel( token ); + continue; + } + + ParseOneResponse( responseGroupName, newGroup ); + } + + m_Responses.Insert( responseGroupName, newGroup ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *criterion - +//----------------------------------------------------------------------------- +int CResponseSystem::ParseOneCriterion( const char *criterionName ) +{ + char key[ 128 ]; + char value[ 128 ]; + + Criteria newCriterion; + + bool gotbody = false; + + while ( TokenWaiting() || !gotbody ) + { + ParseToken(); + + // Oops, part of next definition + if( IsRootCommand() ) + { + Unget(); + break; + } + + if ( !Q_stricmp( token, "{" ) ) + { + gotbody = true; + + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + break; + + // Look up subcriteria index + int idx = m_Criteria.Find( token ); + if ( idx != m_Criteria.InvalidIndex() ) + { + newCriterion.subcriteria.AddToTail( idx ); + } + else + { + ResponseWarning( "Skipping unrecongized subcriterion '%s' in '%s'\n", token, criterionName ); + } + } + continue; + } + else if ( !Q_stricmp( token, "required" ) ) + { + newCriterion.required = true; + } + else if ( !Q_stricmp( token, "weight" ) ) + { + ParseToken(); + newCriterion.weight.SetFloat( (float)atof( token ) ); + } + else + { + Assert( newCriterion.subcriteria.Count() == 0 ); + + // Assume it's the math info for a non-subcriteria resposne + Q_strncpy( key, token, sizeof( key ) ); + ParseToken(); + Q_strncpy( value, token, sizeof( value ) ); + + newCriterion.name = CopyString( key ); + newCriterion.value = CopyString( value ); + + gotbody = true; + } + } + + if ( !newCriterion.IsSubCriteriaType() ) + { + ComputeMatcher( &newCriterion, newCriterion.matcher ); + } + + if ( m_Criteria.Find( criterionName ) != m_Criteria.InvalidIndex() ) + { + ResponseWarning( "Multiple definitions for criteria '%s'\n", criterionName ); + return m_Criteria.InvalidIndex(); + } + + int idx = m_Criteria.Insert( criterionName, newCriterion ); + return idx; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *kv - +//----------------------------------------------------------------------------- +void CResponseSystem::ParseCriterion( void ) +{ + // Should have groupname at start + char criterionName[ 128 ]; + ParseToken(); + Q_strncpy( criterionName, token, sizeof( criterionName ) ); + + ParseOneCriterion( criterionName ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *kv - +//----------------------------------------------------------------------------- +void CResponseSystem::ParseEnumeration( void ) +{ + char enumerationName[ 128 ]; + ParseToken(); + Q_strncpy( enumerationName, token, sizeof( enumerationName ) ); + + ParseToken(); + if ( Q_stricmp( token, "{" ) ) + { + ResponseWarning( "Expecting '{' in enumeration '%s', got '%s'\n", enumerationName, token ); + return; + } + + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + break; + + if ( Q_strlen( token ) <= 0 ) + { + ResponseWarning( "Expecting more tokens in enumeration '%s'\n", enumerationName ); + break; + } + + char key[ 128 ]; + + Q_strncpy( key, token, sizeof( key ) ); + ParseToken(); + float value = (float)atof( token ); + + char sz[ 128 ]; + Q_snprintf( sz, sizeof( sz ), "[%s::%s]", enumerationName, key ); + Q_strlower( sz ); + + Enumeration newEnum; + newEnum.value = value; + + if ( m_Enumerations.Find( sz ) == m_Enumerations.InvalidIndex() ) + { + m_Enumerations.Insert( sz, newEnum ); + } + /* + else + { + ResponseWarning( "Ignoring duplication enumeration '%s'\n", sz ); + } + */ + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *kv - +//----------------------------------------------------------------------------- +void CResponseSystem::ParseRule( void ) +{ + static int instancedCriteria = 0; + + char ruleName[ 128 ]; + ParseToken(); + Q_strncpy( ruleName, token, sizeof( ruleName ) ); + + ParseToken(); + if ( Q_stricmp( token, "{" ) ) + { + ResponseWarning( "Expecting '{' in rule '%s', got '%s'\n", ruleName, token ); + return; + } + + // entries are "criteria", "response" or an in-line criteria to instance + Rule newRule; + + char sz[ 128 ]; + + bool validRule = true; + while ( 1 ) + { + ParseToken(); + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + if ( Q_strlen( token ) <= 0 ) + { + ResponseWarning( "Expecting more tokens in rule '%s'\n", ruleName ); + break; + } + + if ( !Q_stricmp( token, "matchonce" ) ) + { + newRule.m_bMatchOnce = true; + continue; + } + + if ( !Q_stricmp( token, "applyContextToWorld" ) ) + { + newRule.m_bApplyContextToWorld = true; + continue; + } + + if ( !Q_stricmp( token, "applyContext" ) ) + { + ParseToken(); + if ( newRule.GetContext() == NULL ) + { + newRule.SetContext( token ); + } + else + { + CFmtStrN<1024> newContext( "%s,%s", newRule.GetContext(), token ); + newRule.SetContext( newContext ); + } + continue; + } + + if ( !Q_stricmp( token, "response" ) ) + { + // Read them until we run out. + while ( TokenWaiting() ) + { + ParseToken(); + int idx = m_Responses.Find( token ); + if ( idx != m_Responses.InvalidIndex() ) + { + MEM_ALLOC_CREDIT(); + newRule.m_Responses.AddToTail( idx ); + } + else + { + validRule = false; + ResponseWarning( "No such response '%s' for rule '%s'\n", token, ruleName ); + } + } + continue; + } + + if ( !Q_stricmp( token, "criteria" ) || + !Q_stricmp( token, "criterion" ) ) + { + // Read them until we run out. + while ( TokenWaiting() ) + { + ParseToken(); + + int idx = m_Criteria.Find( token ); + if ( idx != m_Criteria.InvalidIndex() ) + { + MEM_ALLOC_CREDIT(); + newRule.m_Criteria.AddToTail( idx ); + } + else + { + validRule = false; + ResponseWarning( "No such criterion '%s' for rule '%s'\n", token, ruleName ); + } + } + continue; + } + + // It's an inline criteria, generate a name and parse it in + Q_snprintf( sz, sizeof( sz ), "[%s%03i]", ruleName, ++instancedCriteria ); + Unget(); + int idx = ParseOneCriterion( sz ); + if ( idx != m_Criteria.InvalidIndex() ) + { + newRule.m_Criteria.AddToTail( idx ); + } + } + + if ( validRule ) + { + m_Rules.Insert( ruleName, newRule ); + } + else + { + DevMsg( "Discarded rule %s\n", ruleName ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CResponseSystem::GetCurrentToken() const +{ + if ( m_ScriptStack.Count() <= 0 ) + return -1; + + return m_ScriptStack[ 0 ].tokencount; +} + + +void CResponseSystem::ResponseWarning( const char *fmt, ... ) +{ + va_list argptr; +#ifndef _XBOX + static char string[1024]; +#else + char string[1024]; +#endif + + va_start (argptr, fmt); + Q_vsnprintf(string, sizeof(string), fmt,argptr); + va_end (argptr); + + char cur[ 256 ]; + GetCurrentScript( cur, sizeof( cur ) ); + DevMsg( 1, "%s(token %i) : %s", cur, GetCurrentToken(), string ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::CopyCriteriaFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ) +{ + // Add criteria from this rule to global list in custom response system. + int nCriteriaCount = pSrcRule->m_Criteria.Count(); + for ( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria ) + { + int iSrcIndex = pSrcRule->m_Criteria[iCriteria]; + Criteria *pSrcCriteria = &m_Criteria[iSrcIndex]; + if ( pSrcCriteria ) + { + int iIndex = pCustomSystem->m_Criteria.Find( m_Criteria.GetElementName( iSrcIndex ) ); + if ( iIndex != pCustomSystem->m_Criteria.InvalidIndex() ) + { + pDstRule->m_Criteria.AddToTail( iIndex ); + continue; + } + + // Add the criteria. + Criteria dstCriteria; + + dstCriteria.name = CopyString( pSrcCriteria->name ); + dstCriteria.value = CopyString( pSrcCriteria->value ); + dstCriteria.weight = pSrcCriteria->weight; + dstCriteria.required = pSrcCriteria->required; + dstCriteria.matcher = pSrcCriteria->matcher; + + int nSubCriteriaCount = pSrcCriteria->subcriteria.Count(); + for ( int iSubCriteria = 0; iSubCriteria < nSubCriteriaCount; ++iSubCriteria ) + { + int iSrcSubIndex = pSrcCriteria->subcriteria[iSubCriteria]; + Criteria *pSrcSubCriteria = &m_Criteria[iSrcSubIndex]; + if ( pSrcCriteria ) + { + int iSubIndex = pCustomSystem->m_Criteria.Find( pSrcSubCriteria->value ); + if ( iSubIndex != pCustomSystem->m_Criteria.InvalidIndex() ) + continue; + + // Add the criteria. + Criteria dstSubCriteria; + + dstSubCriteria.name = CopyString( pSrcSubCriteria->name ); + dstSubCriteria.value = CopyString( pSrcSubCriteria->value ); + dstSubCriteria.weight = pSrcSubCriteria->weight; + dstSubCriteria.required = pSrcSubCriteria->required; + dstSubCriteria.matcher = pSrcSubCriteria->matcher; + + int iSubInsertIndex = pCustomSystem->m_Criteria.Insert( pSrcSubCriteria->value, dstSubCriteria ); + dstCriteria.subcriteria.AddToTail( iSubInsertIndex ); + } + } + + int iInsertIndex = pCustomSystem->m_Criteria.Insert( m_Criteria.GetElementName( iSrcIndex ), dstCriteria ); + pDstRule->m_Criteria.AddToTail( iInsertIndex ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::CopyResponsesFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem ) +{ + // Add responses from this rule to global list in custom response system. + int nResponseGroupCount = pSrcRule->m_Responses.Count(); + for ( int iResponseGroup = 0; iResponseGroup < nResponseGroupCount; ++iResponseGroup ) + { + int iSrcResponseGroup = pSrcRule->m_Responses[iResponseGroup]; + ResponseGroup *pSrcResponseGroup = &m_Responses[iSrcResponseGroup]; + if ( pSrcResponseGroup ) + { + // Add response group. + ResponseGroup dstResponseGroup; + + dstResponseGroup.rp = pSrcResponseGroup->rp; + dstResponseGroup.m_bDepleteBeforeRepeat = pSrcResponseGroup->m_bDepleteBeforeRepeat; + dstResponseGroup.m_nDepletionCount = pSrcResponseGroup->m_nDepletionCount; + dstResponseGroup.m_bHasFirst = pSrcResponseGroup->m_bHasFirst; + dstResponseGroup.m_bHasLast = pSrcResponseGroup->m_bHasLast; + dstResponseGroup.m_bSequential = pSrcResponseGroup->m_bSequential; + dstResponseGroup.m_bNoRepeat = pSrcResponseGroup->m_bNoRepeat; + dstResponseGroup.m_bEnabled = pSrcResponseGroup->m_bEnabled; + dstResponseGroup.m_nCurrentIndex = pSrcResponseGroup->m_nCurrentIndex; + + int nSrcResponseCount = pSrcResponseGroup->group.Count(); + for ( int iResponse = 0; iResponse < nSrcResponseCount; ++iResponse ) + { + Response *pSrcResponse = &pSrcResponseGroup->group[iResponse]; + if ( pSrcResponse ) + { + // Add Response + Response dstResponse; + + dstResponse.weight = pSrcResponse->weight; + dstResponse.type = pSrcResponse->type; + dstResponse.value = CopyString( pSrcResponse->value ); + dstResponse.depletioncount = pSrcResponse->depletioncount; + dstResponse.first = pSrcResponse->first; + dstResponse.last = pSrcResponse->last; + + dstResponseGroup.group.AddToTail( dstResponse ); + } + } + + int iInsertIndex = pCustomSystem->m_Responses.Insert( m_Responses.GetElementName( iSrcResponseGroup ), dstResponseGroup ); + pDstRule->m_Responses.AddToTail( iInsertIndex ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::CopyEnumerationsFrom( CResponseSystem *pCustomSystem ) +{ + int nEnumerationCount = m_Enumerations.Count(); + for ( int iEnumeration = 0; iEnumeration < nEnumerationCount; ++iEnumeration ) + { + Enumeration *pSrcEnumeration = &m_Enumerations[iEnumeration]; + if ( pSrcEnumeration ) + { + Enumeration dstEnumeration; + dstEnumeration.value = pSrcEnumeration->value; + pCustomSystem->m_Enumerations.Insert( m_Enumerations.GetElementName( iEnumeration ), dstEnumeration ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CResponseSystem::CopyRuleFrom( Rule *pSrcRule, int iRule, CResponseSystem *pCustomSystem ) +{ + // Verify data. + Assert( pSrcRule ); + Assert( pCustomSystem ); + if ( !pSrcRule || !pCustomSystem ) + return; + + // New rule + Rule dstRule; + + dstRule.SetContext( pSrcRule->GetContext() ); + dstRule.m_bMatchOnce = pSrcRule->m_bMatchOnce; + dstRule.m_bEnabled = pSrcRule->m_bEnabled; + dstRule.m_bApplyContextToWorld = pSrcRule->m_bApplyContextToWorld; + + // Copy off criteria. + CopyCriteriaFrom( pSrcRule, &dstRule, pCustomSystem ); + + // Copy off responses. + CopyResponsesFrom( pSrcRule, &dstRule, pCustomSystem ); + + // Copy off enumerations - Don't think we use these. +// CopyEnumerationsFrom( pCustomSystem ); + + // Add rule. + pCustomSystem->m_Rules.Insert( m_Rules.GetElementName( iRule ), dstRule ); +} + +//----------------------------------------------------------------------------- +// Purpose: A special purpose response system associated with a custom entity +//----------------------------------------------------------------------------- +class CInstancedResponseSystem : public CResponseSystem +{ + typedef CResponseSystem BaseClass; + +public: + CInstancedResponseSystem( const char *scriptfile ) : + m_pszScriptFile( 0 ) + { + Assert( scriptfile ); + + int len = Q_strlen( scriptfile ) + 1; + m_pszScriptFile = new char[ len ]; + Assert( m_pszScriptFile ); + Q_strncpy( m_pszScriptFile, scriptfile, len ); + } + + ~CInstancedResponseSystem() + { + delete[] m_pszScriptFile; + } + virtual const char *GetScriptFile( void ) + { + Assert( m_pszScriptFile ); + return m_pszScriptFile; + } + + // CAutoGameSystem + virtual bool Init() + { + const char *basescript = GetScriptFile(); + LoadRuleSet( basescript ); + return true; + } + + virtual void LevelInitPostEntity() + { + ResetResponseGroups(); + } + + virtual void Release() + { + Clear(); + delete this; + } +private: + + char *m_pszScriptFile; +}; + +//----------------------------------------------------------------------------- +// Purpose: The default response system for expressive AIs +//----------------------------------------------------------------------------- +class CDefaultResponseSystem : public CResponseSystem, public CAutoGameSystem +{ + typedef CAutoGameSystem BaseClass; + +public: + CDefaultResponseSystem() : CAutoGameSystem( "CDefaultResponseSystem" ) + { + } + + virtual const char *GetScriptFile( void ) + { + return "scripts/talker/response_rules.txt"; + } + + // CAutoServerSystem + virtual bool Init(); + virtual void Shutdown(); + + virtual void LevelInitPostEntity() + { + } + + virtual void Release() + { + Assert( 0 ); + } + + void AddInstancedResponseSystem( const char *scriptfile, CInstancedResponseSystem *sys ) + { + m_InstancedSystems.Insert( scriptfile, sys ); + } + + CInstancedResponseSystem *FindResponseSystem( const char *scriptfile ) + { + int idx = m_InstancedSystems.Find( scriptfile ); + if ( idx == m_InstancedSystems.InvalidIndex() ) + return NULL; + return m_InstancedSystems[ idx ]; + } + + IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ) + { + CInstancedResponseSystem *sys = ( CInstancedResponseSystem * )FindResponseSystem( scriptfile ); + if ( !sys ) + { + sys = new CInstancedResponseSystem( scriptfile ); + if ( !sys ) + { + Error( "Failed to load response system data from %s", scriptfile ); + } + + if ( !sys->Init() ) + { + Error( "CInstancedResponseSystem: Failed to init response system from %s!", scriptfile ); + } + + AddInstancedResponseSystem( scriptfile, sys ); + } + + sys->Precache(); + + return ( IResponseSystem * )sys; + } + + IResponseSystem *BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ); + void DestroyCustomResponseSystems(); + + virtual void LevelInitPreEntity() + { + // This will precache the default system + // All user installed systems are init'd by PrecacheCustomResponseSystem which will call sys->Precache() on the ones being used + + // FIXME: This is SLOW the first time you run the engine (can take 3 - 10 seconds!!!) + if ( ShouldPrecache() ) + { + Precache(); + } + + ResetResponseGroups(); + } + + void ReloadAllResponseSystems() + { + Clear(); + Init(); + + int c = m_InstancedSystems.Count(); + for ( int i = c - 1 ; i >= 0; i-- ) + { + CInstancedResponseSystem *sys = m_InstancedSystems[ i ]; + if ( !IsCustomManagable() ) + { + sys->Clear(); + sys->Init(); + } + else + { + // Custom reponse rules will manage/reload themselves - remove them. + m_InstancedSystems.RemoveAt( i ); + } + } + + } + +private: + + void ClearInstanced() + { + int c = m_InstancedSystems.Count(); + for ( int i = c - 1 ; i >= 0; i-- ) + { + CInstancedResponseSystem *sys = m_InstancedSystems[ i ]; + sys->Release(); + } + m_InstancedSystems.RemoveAll(); + } + + CUtlDict< CInstancedResponseSystem *, int > m_InstancedSystems; +}; + +IResponseSystem *CDefaultResponseSystem::BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ) +{ + // Create a instanced response system. + CInstancedResponseSystem *pCustomSystem = new CInstancedResponseSystem( pszCustomName ); + if ( !pCustomSystem ) + { + Error( "BuildCustomResponseSystemGivenCriterea: Failed to create custom response system %s!", pszCustomName ); + } + + pCustomSystem->Clear(); + + // Copy the relevant rules and data. + int nRuleCount = m_Rules.Count(); + for ( int iRule = 0; iRule < nRuleCount; ++iRule ) + { + Rule *pRule = &m_Rules[iRule]; + if ( pRule ) + { + float flScore = 0.0f; + + int nCriteriaCount = pRule->m_Criteria.Count(); + for ( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria ) + { + int iRuleCriteria = pRule->m_Criteria[iCriteria]; + + flScore += LookForCriteria( criteriaSet, iRuleCriteria ); + if ( flScore >= flCriteriaScore ) + { + CopyRuleFrom( pRule, iRule, pCustomSystem ); + break; + } + } + } + } + + // Set as a custom response system. + m_bCustomManagable = true; + AddInstancedResponseSystem( pszCustomName, pCustomSystem ); + +// pCustomSystem->DumpDictionary( pszCustomName ); + + return pCustomSystem; +} + +void CDefaultResponseSystem::DestroyCustomResponseSystems() +{ + ClearInstanced(); +} + + +static CDefaultResponseSystem defaultresponsesytem; +IResponseSystem *g_pResponseSystem = &defaultresponsesytem; + +CON_COMMAND( rr_reloadresponsesystems, "Reload all response system scripts." ) +{ + if ( !UTIL_IsCommandIssuedByServerAdmin() ) + return; + + defaultresponsesytem.ReloadAllResponseSystems(); + +#if defined( TF_DLL ) + // This is kind of hacky, but I need to get it in for now! + if( g_pGameRules->IsMultiplayer() ) + { + CMultiplayRules *pMultiplayRules = static_cast( g_pGameRules ); + pMultiplayRules->InitCustomResponseRulesDicts(); + } +#endif +} + +static short RESPONSESYSTEM_SAVE_RESTORE_VERSION = 1; + +// note: this won't save/restore settings from instanced response systems. Could add that with a CDefSaveRestoreOps implementation if needed +// +class CDefaultResponseSystemSaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler +{ +public: + const char *GetBlockName() + { + return "ResponseSystem"; + } + + void WriteSaveHeaders( ISave *pSave ) + { + pSave->WriteShort( &RESPONSESYSTEM_SAVE_RESTORE_VERSION ); + } + + void ReadRestoreHeaders( IRestore *pRestore ) + { + // No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so. + short version; + pRestore->ReadShort( &version ); + m_fDoLoad = ( version == RESPONSESYSTEM_SAVE_RESTORE_VERSION ); + } + + void Save( ISave *pSave ) + { + CDefaultResponseSystem& rs = defaultresponsesytem; + + int count = rs.m_Responses.Count(); + pSave->WriteInt( &count ); + for ( int i = 0; i < count; ++i ) + { + pSave->StartBlock( "ResponseGroup" ); + + pSave->WriteString( rs.m_Responses.GetElementName( i ) ); + const ResponseGroup *group = &rs.m_Responses[ i ]; + pSave->WriteAll( group ); + + short groupCount = group->group.Count(); + pSave->WriteShort( &groupCount ); + for ( int j = 0; j < groupCount; ++j ) + { + const Response *response = &group->group[ j ]; + pSave->StartBlock( "Response" ); + pSave->WriteString( response->value ); + pSave->WriteAll( response ); + pSave->EndBlock(); + } + + pSave->EndBlock(); + } + } + + void Restore( IRestore *pRestore, bool createPlayers ) + { + if ( !m_fDoLoad ) + return; + + CDefaultResponseSystem& rs = defaultresponsesytem; + + int count = pRestore->ReadInt(); + for ( int i = 0; i < count; ++i ) + { + char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF]; + pRestore->StartBlock( szResponseGroupBlockName ); + if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) ) + { + + char groupname[ 256 ]; + pRestore->ReadString( groupname, sizeof( groupname ), 0 ); + + // Try and find it + int idx = rs.m_Responses.Find( groupname ); + if ( idx != rs.m_Responses.InvalidIndex() ) + { + ResponseGroup *group = &rs.m_Responses[ idx ]; + pRestore->ReadAll( group ); + + short groupCount = pRestore->ReadShort(); + for ( int j = 0; j < groupCount; ++j ) + { + char szResponseBlockName[SIZE_BLOCK_NAME_BUF]; + + char responsename[ 256 ]; + pRestore->StartBlock( szResponseBlockName ); + if ( !Q_stricmp( szResponseBlockName, "Response" ) ) + { + pRestore->ReadString( responsename, sizeof( responsename ), 0 ); + + // Find it by name + int ri; + for ( ri = 0; ri < group->group.Count(); ++ri ) + { + Response *response = &group->group[ ri ]; + if ( !Q_stricmp( response->value, responsename ) ) + { + break; + } + } + + if ( ri < group->group.Count() ) + { + Response *response = &group->group[ ri ]; + pRestore->ReadAll( response ); + } + } + + pRestore->EndBlock(); + } + } + } + + pRestore->EndBlock(); + } + } +private: + + bool m_fDoLoad; + +} g_DefaultResponseSystemSaveRestoreBlockHandler; + +ISaveRestoreBlockHandler *GetDefaultResponseSystemSaveRestoreBlockHandler() +{ + return &g_DefaultResponseSystemSaveRestoreBlockHandler; +} + +//----------------------------------------------------------------------------- +// CResponseSystemSaveRestoreOps +// +// Purpose: Handles save and load for instanced response systems... +// +// BUGBUG: This will save the same response system to file multiple times for "shared" response systems and +// therefore it'll restore the same data onto the same pointer N times on reload (probably benign for now, but we could +// write code to save/restore the instanced ones by filename in the block handler above maybe? +//----------------------------------------------------------------------------- + +class CResponseSystemSaveRestoreOps : public CDefSaveRestoreOps +{ +public: + + virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) + { + CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField; + if ( !pRS || pRS == &defaultresponsesytem ) + return; + + int count = pRS->m_Responses.Count(); + pSave->WriteInt( &count ); + for ( int i = 0; i < count; ++i ) + { + pSave->StartBlock( "ResponseGroup" ); + + pSave->WriteString( pRS->m_Responses.GetElementName( i ) ); + const ResponseGroup *group = &pRS->m_Responses[ i ]; + pSave->WriteAll( group ); + + short groupCount = group->group.Count(); + pSave->WriteShort( &groupCount ); + for ( int j = 0; j < groupCount; ++j ) + { + const Response *response = &group->group[ j ]; + pSave->StartBlock( "Response" ); + pSave->WriteString( response->value ); + pSave->WriteAll( response ); + pSave->EndBlock(); + } + + pSave->EndBlock(); + } + } + + virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) + { + CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField; + if ( !pRS || pRS == &defaultresponsesytem ) + return; + + int count = pRestore->ReadInt(); + for ( int i = 0; i < count; ++i ) + { + char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF]; + pRestore->StartBlock( szResponseGroupBlockName ); + if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) ) + { + + char groupname[ 256 ]; + pRestore->ReadString( groupname, sizeof( groupname ), 0 ); + + // Try and find it + int idx = pRS->m_Responses.Find( groupname ); + if ( idx != pRS->m_Responses.InvalidIndex() ) + { + ResponseGroup *group = &pRS->m_Responses[ idx ]; + pRestore->ReadAll( group ); + + short groupCount = pRestore->ReadShort(); + for ( int j = 0; j < groupCount; ++j ) + { + char szResponseBlockName[SIZE_BLOCK_NAME_BUF]; + + char responsename[ 256 ]; + pRestore->StartBlock( szResponseBlockName ); + if ( !Q_stricmp( szResponseBlockName, "Response" ) ) + { + pRestore->ReadString( responsename, sizeof( responsename ), 0 ); + + // Find it by name + int ri; + for ( ri = 0; ri < group->group.Count(); ++ri ) + { + Response *response = &group->group[ ri ]; + if ( !Q_stricmp( response->value, responsename ) ) + { + break; + } + } + + if ( ri < group->group.Count() ) + { + Response *response = &group->group[ ri ]; + pRestore->ReadAll( response ); + } + } + + pRestore->EndBlock(); + } + } + } + + pRestore->EndBlock(); + } + } + +} g_ResponseSystemSaveRestoreOps; + +ISaveRestoreOps *responseSystemSaveRestoreOps = &g_ResponseSystemSaveRestoreOps; + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CDefaultResponseSystem::Init() +{ +/* + Warning( "sizeof( Response ) == %d\n", sizeof( Response ) ); + Warning( "sizeof( ResponseGroup ) == %d\n", sizeof( ResponseGroup ) ); + Warning( "sizeof( Criteria ) == %d\n", sizeof( Criteria ) ); + Warning( "sizeof( AI_ResponseParams ) == %d\n", sizeof( AI_ResponseParams ) ); +*/ + const char *basescript = GetScriptFile(); + + LoadRuleSet( basescript ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CDefaultResponseSystem::Shutdown() +{ + // Wipe instanced versions + ClearInstanced(); + + // Clear outselves + Clear(); + // IServerSystem chain + BaseClass::Shutdown(); +} + +//----------------------------------------------------------------------------- +// Purpose: Instance a custom response system +// Input : *scriptfile - +// Output : IResponseSystem +//----------------------------------------------------------------------------- +IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ) +{ + return defaultresponsesytem.PrecacheCustomResponseSystem( scriptfile ); +} + +//----------------------------------------------------------------------------- +// Purpose: Instance a custom response system +// Input : *scriptfile - +// set - +// Output : IResponseSystem +//----------------------------------------------------------------------------- +IResponseSystem *BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ) +{ + return defaultresponsesytem.BuildCustomResponseSystemGivenCriteria( pszBaseFile, pszCustomName, criteriaSet, flCriteriaScore ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void DestroyCustomResponseSystems() +{ + defaultresponsesytem.DestroyCustomResponseSystems(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CResponseSystem::DumpRules() +{ + int c = m_Rules.Count(); + int i; + + for ( i = 0; i < c; i++ ) + { + Msg("%s\n", m_Rules.GetElementName( i ) ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CResponseSystem::DumpDictionary( const char *pszName ) +{ + Msg( "\nDictionary: %s\n", pszName ); + + int nRuleCount = m_Rules.Count(); + for ( int iRule = 0; iRule < nRuleCount; ++iRule ) + { + Msg(" Rule %d: %s\n", iRule, m_Rules.GetElementName( iRule ) ); + + Rule *pRule = &m_Rules[iRule]; + + int nCriteriaCount = pRule->m_Criteria.Count(); + for( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria ) + { + int iRuleCriteria = pRule->m_Criteria[iCriteria]; + Criteria *pCriteria = &m_Criteria[iRuleCriteria]; + Msg( " Criteria %d: %s %s\n", iCriteria, pCriteria->name, pCriteria->value ); + } + + int nResponseGroupCount = pRule->m_Responses.Count(); + for ( int iResponseGroup = 0; iResponseGroup < nResponseGroupCount; ++iResponseGroup ) + { + int iRuleResponse = pRule->m_Responses[iResponseGroup]; + ResponseGroup *pResponseGroup = &m_Responses[iRuleResponse]; + + Msg( " ResponseGroup %d: %s\n", iResponseGroup, m_Responses.GetElementName( iRuleResponse ) ); + + int nResponseCount = pResponseGroup->group.Count(); + for ( int iResponse = 0; iResponse < nResponseCount; ++iResponse ) + { + Response *pResponse = &pResponseGroup->group[iResponse]; + Msg( " Response %d: %s\n", iResponse, pResponse->value ); + } + } + } +} -- cgit v1.2.3