diff options
Diffstat (limited to 'mp/src/game/server/AI_ResponseSystem.cpp')
| -rw-r--r-- | mp/src/game/server/AI_ResponseSystem.cpp | 6808 |
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 ); + } + } + } +} |