aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/AI_ResponseSystem.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/AI_ResponseSystem.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/AI_ResponseSystem.cpp')
-rw-r--r--mp/src/game/server/AI_ResponseSystem.cpp6808
1 files changed, 3404 insertions, 3404 deletions
diff --git a/mp/src/game/server/AI_ResponseSystem.cpp b/mp/src/game/server/AI_ResponseSystem.cpp
index aa32f49d..510a1d4e 100644
--- a/mp/src/game/server/AI_ResponseSystem.cpp
+++ b/mp/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 <KeyValues.h>
-#include "filesystem.h"
-#include "utldict.h"
-#include "ai_speech.h"
-#include "tier0/icommandline.h"
-#include <ctype.h>
-#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<AI_Response *> *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<int> 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<AI_Response *> *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<CMultiplayRules*>( 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 <KeyValues.h>
+#include "filesystem.h"
+#include "utldict.h"
+#include "ai_speech.h"
+#include "tier0/icommandline.h"
+#include <ctype.h>
+#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<AI_Response *> *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<int> 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<AI_Response *> *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<CMultiplayRules*>( 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 );
+ }
+ }
+ }
+}