diff options
Diffstat (limited to 'mp/src/game/server/AI_ResponseSystem.cpp')
| -rw-r--r-- | mp/src/game/server/AI_ResponseSystem.cpp | 3404 |
1 files changed, 3404 insertions, 0 deletions
diff --git a/mp/src/game/server/AI_ResponseSystem.cpp b/mp/src/game/server/AI_ResponseSystem.cpp new file mode 100644 index 00000000..aa32f49d --- /dev/null +++ b/mp/src/game/server/AI_ResponseSystem.cpp @@ -0,0 +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 );
+ }
+ }
+ }
+}
|