source-engine/game/server/AI_ResponseSystem.cpp
FluorescentCIAAfricanAmerican 3bf9df6b27 1
2020-04-22 12:56:21 -04:00

3405 lines
78 KiB
C++

//========= 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 );
}
}
}
}