You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2708 lines
74 KiB
2708 lines
74 KiB
//========= Copyright © 1996-2010, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: Core types for the response rules -- criteria, responses, rules, and matchers.
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "rrbase.h"
|
|
#include "vstdlib/random.h"
|
|
#include "utlbuffer.h"
|
|
#include "tier2/interval.h"
|
|
#include "fmtstr.h"
|
|
#include "generichash.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
// #pragma optimize( "", off )
|
|
|
|
using namespace ResponseRules;
|
|
#if RESPONSE_RULES_DEBUG_ENABLED // this is an exploit to crash game servers, it is now disabled
|
|
static void CC_RR_Debug_ResponseConcept_Exclude( const CCommand &args );
|
|
static ConCommand rr_debug_responseconcept_exclude( "rr_debugresponseconcept_exclude", CC_RR_Debug_ResponseConcept_Exclude, "Set a list of concepts to exclude from rr_debugresponseconcept. Separate multiple concepts with spaces. Call with no arguments to see current list. Call 'rr_debug_responseconcept_exclude !' to reset.");
|
|
#endif
|
|
static void CC_RR_DumpHashInfo( const CCommand &args );
|
|
|
|
namespace ResponseRules
|
|
{
|
|
// ick
|
|
// Wrap string lookup with a hash on the string so that all of the repetitive playerxxx type strings get bucketed out better
|
|
#define STRING_BUCKETS_COUNT 64 // Must be power of two
|
|
#define STRING_BUCKETS_MASK ( STRING_BUCKETS_COUNT - 1 )
|
|
|
|
CUtlRBTree<const char *> g_ResponseStrings[ STRING_BUCKETS_COUNT ];
|
|
class CResponseStringBuckets
|
|
{
|
|
public:
|
|
CResponseStringBuckets()
|
|
{
|
|
for ( int i = 0; i < ARRAYSIZE( g_ResponseStrings ); ++i )
|
|
{
|
|
g_ResponseStrings[ i ].SetLessFunc( &StringLessThan );
|
|
}
|
|
}
|
|
} g_ReponseStringBucketInitializer;
|
|
|
|
const char *ResponseCopyString( const char *in )
|
|
{
|
|
if ( !in )
|
|
return NULL;
|
|
if ( !*in )
|
|
return "";
|
|
|
|
int bucket = ( RR_HASH( in ) & STRING_BUCKETS_MASK );
|
|
|
|
CUtlRBTree<const char *> &list = g_ResponseStrings[ bucket ];
|
|
|
|
int i = list.Find( in );
|
|
if ( i != list.InvalidIndex() )
|
|
{
|
|
return list[i];
|
|
}
|
|
|
|
int len = Q_strlen( in );
|
|
char *out = new char[ len + 1 ];
|
|
Q_memcpy( out, in, len );
|
|
out[ len ] = 0;
|
|
list.Insert( out );
|
|
return out;
|
|
}
|
|
}
|
|
|
|
IEngineEmulator *IEngineEmulator::Get()
|
|
{
|
|
AssertMsg( IEngineEmulator::s_pSingleton, "Response rules fail: no IEngineEmulator" );
|
|
return IEngineEmulator::s_pSingleton;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Convars
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
ConVar rr_debugresponses( "rr_debugresponses", "0", FCVAR_NONE, "Show verbose matching output (1 for simple, 2 for rule scoring, 3 for noisy). If set to 4, 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)" );
|
|
ConVar rr_debugresponseconcept( "rr_debugresponseconcept", "", FCVAR_NONE, "If set, rr_debugresponses will print only responses testing for the specified concept" );
|
|
#define RR_DEBUGRESPONSES_SPECIALCASE 4
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Copied from SoundParametersInternal.cpp
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#define SNDLVL_PREFIX "SNDLVL_"
|
|
|
|
struct SoundLevelLookup
|
|
{
|
|
soundlevel_t level;
|
|
char const *name;
|
|
};
|
|
|
|
// NOTE: Needs to reflect the soundlevel_t enum defined in soundflags.h
|
|
static SoundLevelLookup g_pSoundLevels[] =
|
|
{
|
|
{ SNDLVL_NONE, "SNDLVL_NONE" },
|
|
{ SNDLVL_20dB, "SNDLVL_20dB" },
|
|
{ SNDLVL_25dB, "SNDLVL_25dB" },
|
|
{ SNDLVL_30dB, "SNDLVL_30dB" },
|
|
{ SNDLVL_35dB, "SNDLVL_35dB" },
|
|
{ SNDLVL_40dB, "SNDLVL_40dB" },
|
|
{ SNDLVL_45dB, "SNDLVL_45dB" },
|
|
{ SNDLVL_50dB, "SNDLVL_50dB" },
|
|
{ SNDLVL_55dB, "SNDLVL_55dB" },
|
|
{ SNDLVL_IDLE, "SNDLVL_IDLE" },
|
|
{ SNDLVL_TALKING, "SNDLVL_TALKING" },
|
|
{ SNDLVL_60dB, "SNDLVL_60dB" },
|
|
{ SNDLVL_65dB, "SNDLVL_65dB" },
|
|
{ SNDLVL_STATIC, "SNDLVL_STATIC" },
|
|
{ SNDLVL_70dB, "SNDLVL_70dB" },
|
|
{ SNDLVL_NORM, "SNDLVL_NORM" },
|
|
{ SNDLVL_75dB, "SNDLVL_75dB" },
|
|
{ SNDLVL_80dB, "SNDLVL_80dB" },
|
|
{ SNDLVL_85dB, "SNDLVL_85dB" },
|
|
{ SNDLVL_90dB, "SNDLVL_90dB" },
|
|
{ SNDLVL_95dB, "SNDLVL_95dB" },
|
|
{ SNDLVL_100dB, "SNDLVL_100dB" },
|
|
{ SNDLVL_105dB, "SNDLVL_105dB" },
|
|
{ SNDLVL_110dB, "SNDLVL_110dB" },
|
|
{ SNDLVL_120dB, "SNDLVL_120dB" },
|
|
{ SNDLVL_130dB, "SNDLVL_130dB" },
|
|
{ SNDLVL_GUNFIRE, "SNDLVL_GUNFIRE" },
|
|
{ SNDLVL_140dB, "SNDLVL_140dB" },
|
|
{ SNDLVL_150dB, "SNDLVL_150dB" },
|
|
{ SNDLVL_180dB, "SNDLVL_180dB" },
|
|
};
|
|
|
|
static soundlevel_t TextToSoundLevel( const char *key )
|
|
{
|
|
if ( !key )
|
|
{
|
|
Assert( 0 );
|
|
return SNDLVL_NORM;
|
|
}
|
|
|
|
int c = ARRAYSIZE( g_pSoundLevels );
|
|
|
|
int i;
|
|
|
|
for ( i = 0; i < c; i++ )
|
|
{
|
|
SoundLevelLookup *entry = &g_pSoundLevels[ i ];
|
|
if ( !Q_strcasecmp( key, entry->name ) )
|
|
return entry->level;
|
|
}
|
|
|
|
if ( !Q_strnicmp( key, SNDLVL_PREFIX, Q_strlen( SNDLVL_PREFIX ) ) )
|
|
{
|
|
char const *val = key + Q_strlen( SNDLVL_PREFIX );
|
|
int sndlvl = atoi( val );
|
|
if ( sndlvl > 0 && sndlvl <= 180 )
|
|
{
|
|
return ( soundlevel_t )sndlvl;
|
|
}
|
|
}
|
|
|
|
DevMsg( "CSoundEmitterSystem: Unknown sound level %s\n", key );
|
|
|
|
return SNDLVL_NORM;
|
|
}
|
|
|
|
CResponseSystem::ExcludeList_t CResponseSystem::m_DebugExcludeList( 4, 0 );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
CResponseSystem::CResponseSystem() :
|
|
m_RootCommandHashes( 0, 0, DefLessFunc( unsigned int ) ),
|
|
m_FileDispatch( 0, 0, DefLessFunc( unsigned int ) ),
|
|
m_RuleDispatch( 0, 0, DefLessFunc( unsigned int ) ),
|
|
m_ResponseDispatch( 0, 0, DefLessFunc( unsigned int ) ),
|
|
m_ResponseGroupDispatch( 0, 0, DefLessFunc( unsigned int ) )
|
|
{
|
|
token[0] = 0;
|
|
m_bUnget = false;
|
|
m_bCustomManagable = false;
|
|
|
|
BuildDispatchTables();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 ( IEngineEmulator::Get()->GetFilesystem()->String( m_ScriptStack[ 0 ].name, buf, buflen ) )
|
|
{
|
|
return;
|
|
}
|
|
buf[ 0 ] = 0;
|
|
}
|
|
|
|
void CResponseSystem::PushScript( const char *scriptfile, unsigned char *buffer )
|
|
{
|
|
ScriptEntry e;
|
|
e.name = IEngineEmulator::Get()->GetFilesystem()->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_RulePartitions.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 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 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 CriteriaSet &criteriaSet, int iCriteria )
|
|
{
|
|
Criteria *pCriteria = &m_Criteria[iCriteria];
|
|
if ( pCriteria->IsSubCriteriaType() )
|
|
{
|
|
return RecursiveLookForCriteria( criteriaSet, pCriteria );
|
|
}
|
|
|
|
int iIndex = criteriaSet.FindCriterionIndex( pCriteria->nameSym );
|
|
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 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 ), CriteriaSet::SymbolToStr(c->nameSym) );
|
|
}
|
|
|
|
exclude = false;
|
|
|
|
float score = 0.0f;
|
|
|
|
const char *actualValue = "";
|
|
|
|
/*
|
|
const char * RESTRICT critname = c->name;
|
|
CUtlSymbol sym(critname);
|
|
const char * nameDoubleCheck = sym.String();
|
|
*/
|
|
int found = set.FindCriterionIndex( c->nameSym );
|
|
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 CriteriaSet& set, ResponseRulePartition::tRuleDict &dict, int irule, bool verbose /*=false*/ )
|
|
{
|
|
Rule * RESTRICT rule = dict[ 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, dict.GetElementName( irule ) ) )
|
|
{
|
|
bBeingWatched = true;
|
|
}
|
|
|
|
if ( !rule->IsEnabled() )
|
|
{
|
|
if ( bBeingWatched )
|
|
{
|
|
DevMsg("Rule is disabled.\n" );
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
if ( bBeingWatched )
|
|
{
|
|
verbose = true;
|
|
}
|
|
|
|
if ( verbose )
|
|
{
|
|
DevMsg( "Scoring rule '%s' (%i)\n{\n", dict.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" );
|
|
}
|
|
|
|
if ( rule->m_nForceWeight > 0 )
|
|
{ // this means override the cumulative weight of criteria and just force the rule's total score,
|
|
// assuming it matched at all.
|
|
return fsel( score - FLT_MIN, rule->m_nForceWeight, 0 );
|
|
}
|
|
else
|
|
{
|
|
return score;
|
|
}
|
|
}
|
|
|
|
void CResponseSystem::DebugPrint( int depth, const char *fmt, ... )
|
|
{
|
|
int indentchars = 3 * depth;
|
|
char *indent = (char *) stackalloc( 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: Make certain responses unavailable by marking them as depleted
|
|
//-----------------------------------------------------------------------------
|
|
void CResponseSystem::FakeDepletes( ResponseGroup *g, IResponseFilter *pFilter )
|
|
{
|
|
m_FakedDepletes.RemoveAll();
|
|
|
|
// Fake depletion of unavailable choices
|
|
int c = g->group.Count();
|
|
if ( pFilter && g->ShouldCheckRepeats() )
|
|
{
|
|
for ( int i = 0; i < c; i++ )
|
|
{
|
|
ParserResponse *r = &g->group[ i ];
|
|
if ( r->depletioncount != g->GetDepletionCount() && !pFilter->IsValidResponse( r->GetType(), r->value ) )
|
|
{
|
|
m_FakedDepletes.AddToTail( i );
|
|
g->MarkResponseUsed( i );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fake depletion of choices that fail the odds check
|
|
for ( int i = 0; i < c; i++ )
|
|
{
|
|
ParserResponse *r = &g->group[ i ];
|
|
if ( RandomInt( 1, 100 ) > r->params.odds )
|
|
{
|
|
m_FakedDepletes.AddToTail( i );
|
|
g->MarkResponseUsed( i );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Restore responses that were faked as being depleted
|
|
//-----------------------------------------------------------------------------
|
|
void CResponseSystem::RevertFakedDepletes( ResponseGroup *g )
|
|
{
|
|
for ( int i = 0; i < m_FakedDepletes.Count(); i++ )
|
|
{
|
|
g->group[ m_FakedDepletes[ i ] ].depletioncount = 0;
|
|
}
|
|
m_FakedDepletes.RemoveAll();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
FakeDepletes( g, pFilter );
|
|
|
|
if ( !g->HasUndepletedChoices() )
|
|
{
|
|
g->ResetDepletionCount();
|
|
|
|
FakeDepletes( g, pFilter );
|
|
|
|
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
|
|
int i;
|
|
for ( i = 0; i < c; i++ )
|
|
{
|
|
ParserResponse *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 ( int i = 0; i < c; i++ )
|
|
{
|
|
ParserResponse *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 || IEngineEmulator::Get()->GetRandomStream()->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
|
|
RevertFakedDepletes( g );
|
|
|
|
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;
|
|
|
|
ParserResponse *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++ )
|
|
{
|
|
ParserResponse *r = &group->group[ i ];
|
|
DebugPrint( depth + 1, "%s%20s : %40s %5.3f\n",
|
|
i == selected ? "-> " : " ",
|
|
CRR_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 = IEngineEmulator::Get()->GetRandomStream()->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;
|
|
}
|
|
|
|
|
|
ParserResponse *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
|
|
// Warning: If you change this, be sure to also change
|
|
// ResponseSystemImplementationCLI::FindAllRulesMatchingCriteria().
|
|
//-----------------------------------------------------------------------------
|
|
ResponseRulePartition::tIndex CResponseSystem::FindBestMatchingRule( const CriteriaSet& set, bool verbose, float &scoreOfBestMatchingRule )
|
|
{
|
|
CUtlVector< ResponseRulePartition::tIndex > bestrules(16,4);
|
|
float bestscore = 0.001f;
|
|
scoreOfBestMatchingRule = 0;
|
|
|
|
CUtlVectorFixed< ResponseRulePartition::tRuleDict *, 2 > buckets( 0, 2 );
|
|
m_RulePartitions.GetDictsForCriteria( &buckets, set );
|
|
for ( int b = 0 ; b < buckets.Count() ; ++b )
|
|
{
|
|
ResponseRulePartition::tRuleDict *prules = buckets[b];
|
|
int c = prules->Count();
|
|
int i;
|
|
for ( i = 0; i < c; i++ )
|
|
{
|
|
float score = ScoreCriteriaAgainstRule( set, *prules, 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( m_RulePartitions.IndexFromDictElem( prules, i ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
int bestCount = bestrules.Count();
|
|
if ( bestCount <= 0 )
|
|
return m_RulePartitions.InvalidIdx();
|
|
|
|
scoreOfBestMatchingRule = bestscore ;
|
|
if ( bestCount == 1 )
|
|
{
|
|
return bestrules[ 0 ] ;
|
|
}
|
|
else
|
|
{
|
|
// Randomly pick one of the tied matching rules
|
|
int idx = IEngineEmulator::Get()->GetRandomStream()->RandomInt( 0, bestCount - 1 );
|
|
if ( verbose )
|
|
{
|
|
DevMsg( "Found %i matching rules, selecting slot %i\n", bestCount, idx );
|
|
}
|
|
return bestrules[ idx ] ;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : set -
|
|
// Output : CRR_Response
|
|
//-----------------------------------------------------------------------------
|
|
bool CResponseSystem::FindBestResponse( const CriteriaSet& set, CRR_Response& response, IResponseFilter *pFilter )
|
|
{
|
|
bool valid = false;
|
|
|
|
int iDbgResponse = rr_debugresponses.GetInt();
|
|
bool showRules = ( iDbgResponse >= 2 && iDbgResponse < RR_DEBUGRESPONSES_SPECIALCASE );
|
|
bool showResult = ( iDbgResponse >= 1 && iDbgResponse < RR_DEBUGRESPONSES_SPECIALCASE );
|
|
|
|
// Look for match. verbose mode used to be at level 2, but disabled because the writers don't actually care for that info.
|
|
float scoreOfBestRule;
|
|
ResponseRulePartition::tIndex bestRule = FindBestMatchingRule( set,
|
|
( iDbgResponse >= 3 && iDbgResponse < RR_DEBUGRESPONSES_SPECIALCASE ),
|
|
scoreOfBestRule );
|
|
|
|
ResponseType_t responseType = RESPONSE_NONE;
|
|
ResponseParams rp;
|
|
|
|
char ruleName[ 128 ];
|
|
char responseName[ 128 ];
|
|
const char *context;
|
|
bool bcontexttoworld;
|
|
ruleName[ 0 ] = 0;
|
|
responseName[ 0 ] = 0;
|
|
context = NULL;
|
|
bcontexttoworld = false;
|
|
if ( m_RulePartitions.IsValid( bestRule ) )
|
|
{
|
|
Rule * RESTRICT r = &m_RulePartitions[ bestRule ];
|
|
|
|
ResponseSearchResult result;
|
|
if ( GetBestResponse( result, r, showResult, pFilter ) )
|
|
{
|
|
Q_strncpy( responseName, result.action->value, sizeof( responseName ) );
|
|
responseType = result.action->GetType();
|
|
rp = result.action->params;
|
|
rp.m_pFollowup = &result.action->m_followup;
|
|
}
|
|
|
|
Q_strncpy( ruleName, m_RulePartitions.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();
|
|
|
|
response.SetMatchScore(scoreOfBestRule);
|
|
valid = true;
|
|
}
|
|
|
|
response.Init( responseType, responseName, 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 )
|
|
{
|
|
const char *pConceptFilter = rr_debugresponseconcept.GetString();
|
|
// Describe the response, too
|
|
if ( V_strlen(pConceptFilter) > 0 && !rr_debugresponseconcept.GetBool() )
|
|
{ // filter for only one concept
|
|
if ( V_stricmp(pConceptFilter, set.GetValue(set.FindCriterionIndex("concept")) ) == 0 )
|
|
{
|
|
response.Describe(&set);
|
|
} // else don't print
|
|
}
|
|
else
|
|
{
|
|
// maybe we need to filter *out* some concepts
|
|
if ( m_DebugExcludeList.IsValidIndex( m_DebugExcludeList.Head() ) )
|
|
{
|
|
// we are excluding at least one concept
|
|
CRR_Concept test( set.GetValue(set.FindCriterionIndex("concept")) );
|
|
if ( ! m_DebugExcludeList.IsValidIndex( m_DebugExcludeList.Find( test ) ) )
|
|
{ // if not found in exclude list, then print
|
|
response.Describe(&set);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// describe everything
|
|
response.Describe(&set);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CResponseSystem::GetAllResponses( CUtlVector<CRR_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++)
|
|
{
|
|
ParserResponse &response = group.group[j];
|
|
if ( response.type != RESPONSE_RESPONSE )
|
|
{
|
|
/*
|
|
CRR_Response *pResponse = new CRR_Response;
|
|
pResponse->Init( response.GetType(), response.value, CriteriaSet(), response.params, NULL, NULL, false );
|
|
pResponses->AddToTail(pResponse);
|
|
*/
|
|
pResponses->Element(pResponses->AddToTail()).Init( response.GetType(), response.value, response.params, NULL, NULL, false );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CResponseSystem::ParseInclude()
|
|
{
|
|
char includefile[ 256 ];
|
|
ParseToken();
|
|
Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token );
|
|
|
|
// check if the file is already included
|
|
if ( m_IncludedFiles.Find( includefile ) != NULL )
|
|
{
|
|
return;
|
|
}
|
|
|
|
MEM_ALLOC_CREDIT();
|
|
|
|
// Try and load it
|
|
CUtlBuffer buf;
|
|
if ( !IEngineEmulator::Get()->GetFilesystem()->ReadFile( includefile, "GAME", buf ) )
|
|
{
|
|
DevMsg( "Unable to load #included script %s\n", includefile );
|
|
return;
|
|
}
|
|
|
|
LoadFromBuffer( includefile, (const char *)buf.PeekGet() );
|
|
}
|
|
|
|
void CResponseSystem::LoadFromBuffer( const char *scriptfile, const char *buffer )
|
|
{
|
|
COM_TimestampedLog( "CResponseSystem::LoadFromBuffer [%s] - Start", scriptfile );
|
|
m_IncludedFiles.Allocate( scriptfile );
|
|
PushScript( scriptfile, (unsigned char * )buffer );
|
|
|
|
if( rr_dumpresponses.GetBool() )
|
|
{
|
|
DevMsg("Reading: %s\n", scriptfile );
|
|
}
|
|
|
|
while ( 1 )
|
|
{
|
|
ParseToken();
|
|
if ( !token[0] )
|
|
{
|
|
break;
|
|
}
|
|
|
|
unsigned int hash = RR_HASH( token );
|
|
bool bSuccess = Dispatch( token, hash, m_FileDispatch );
|
|
if ( !bSuccess )
|
|
{
|
|
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_RulePartitions.Count(), m_Criteria.Count(), m_Responses.Count() );
|
|
|
|
if( rr_dumpresponses.GetBool() )
|
|
{
|
|
DumpRules();
|
|
}
|
|
}
|
|
|
|
PopScript();
|
|
COM_TimestampedLog( "CResponseSystem::LoadFromBuffer [%s] - Finish", scriptfile );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CResponseSystem::LoadRuleSet( const char *basescript )
|
|
{
|
|
float flStart = Plat_FloatTime();
|
|
int length = 0;
|
|
unsigned char *buffer = (unsigned char *)IEngineEmulator::Get()->LoadFileForMe( basescript, &length );
|
|
if ( length <= 0 || !buffer )
|
|
{
|
|
DevMsg( 1, "CResponseSystem: failed to load %s\n", basescript );
|
|
return;
|
|
}
|
|
|
|
m_IncludedFiles.FreeAll();
|
|
LoadFromBuffer( basescript, (const char *)buffer );
|
|
|
|
IEngineEmulator::Get()->FreeFile( buffer );
|
|
|
|
Assert( m_ScriptStack.Count() == 0 );
|
|
float flEnd = Plat_FloatTime();
|
|
COM_TimestampedLog( "CResponseSystem::LoadRuleSet took %f msec", 1000.0f * ( flEnd - flStart ) );
|
|
}
|
|
|
|
inline ResponseType_t ComputeResponseType( const char *s )
|
|
{
|
|
switch ( s[ 0 ] )
|
|
{
|
|
default:
|
|
break;
|
|
case 's':
|
|
switch ( s[ 1 ] )
|
|
{
|
|
default:
|
|
break;
|
|
case 'c':
|
|
return RESPONSE_SCENE;
|
|
case 'e':
|
|
return RESPONSE_SENTENCE;
|
|
case 'p':
|
|
return RESPONSE_SPEAK;
|
|
}
|
|
break;
|
|
case 'r':
|
|
return RESPONSE_RESPONSE;
|
|
case 'p':
|
|
return RESPONSE_PRINT;
|
|
case 'e':
|
|
return RESPONSE_ENTITYIO;
|
|
}
|
|
|
|
return RESPONSE_NONE;
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_Weight( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
ParseToken();
|
|
newResponse.weight.SetFloat( (float)atof( token ) );
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_PreDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
ParseToken();
|
|
rp->flags |= AI_ResponseParams::RG_DELAYBEFORESPEAK;
|
|
rp->predelay.FromInterval( ReadInterval( token ) );
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_NoDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
ParseToken();
|
|
rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK;
|
|
rp->delay.start = 0;
|
|
rp->delay.range = 0;
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_DefaultDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK;
|
|
rp->delay.start = AIS_DEF_MIN_DELAY;
|
|
rp->delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY );
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_Delay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
ParseToken();
|
|
rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK;
|
|
rp->delay.FromInterval( ReadInterval( token ) );
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_SpeakOnce( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
rp->flags |= AI_ResponseParams::RG_SPEAKONCE;
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_NoScene( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
rp->flags |= AI_ResponseParams::RG_DONT_USE_SCENE;
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_StopOnNonIdle( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
rp->flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE;
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_Odds( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
ParseToken();
|
|
rp->flags |= AI_ResponseParams::RG_ODDS;
|
|
rp->odds = clamp( atoi( token ), 0, 100 );
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_RespeakDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
ParseToken();
|
|
rp->flags |= AI_ResponseParams::RG_RESPEAKDELAY;
|
|
rp->respeakdelay.FromInterval( ReadInterval( token ) );
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_WeaponDelay( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
ParseToken();
|
|
rp->flags |= AI_ResponseParams::RG_WEAPONDELAY;
|
|
rp->weapondelay.FromInterval( ReadInterval( token ) );
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_Soundlevel( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
ParseToken();
|
|
rp->flags |= AI_ResponseParams::RG_SOUNDLEVEL;
|
|
rp->soundlevel = (soundlevel_t)TextToSoundLevel( token );
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_DisplayFirst( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
newResponse.first = true;
|
|
group.m_bHasFirst = true;
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_DisplayLast( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
newResponse.last = true;
|
|
group.m_bHasLast= true;
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_Fire( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
// get target name
|
|
bool bSuc = ParseToken();
|
|
if (!bSuc)
|
|
{
|
|
ResponseWarning( "FIRE token in response needs exactly three parameters." );
|
|
return;
|
|
}
|
|
newResponse.m_followup.followup_entityiotarget = ResponseCopyString(token);
|
|
|
|
bSuc = ParseToken();
|
|
if (!bSuc)
|
|
{
|
|
ResponseWarning( "FIRE token in response needs exactly three parameters." );
|
|
return;
|
|
}
|
|
newResponse.m_followup.followup_entityioinput = ResponseCopyString(token);
|
|
|
|
bSuc = ParseToken();
|
|
if (!bSuc)
|
|
{
|
|
ResponseWarning( "FIRE token in response needs exactly three parameters." );
|
|
return;
|
|
}
|
|
newResponse.m_followup.followup_entityiodelay = atof( token );
|
|
/*
|
|
m_followup.followup_entityioinput = ResponseCopyString(src.m_followup.followup_entityioinput);
|
|
m_followup.followup_entityiotarget = ResponseCopyString(src.m_followup.followup_entityiotarget);
|
|
*/
|
|
}
|
|
|
|
void CResponseSystem::ParseResponse_Then( ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
// eg, "subject TALK_ANSWER saidunplant:1 3"
|
|
bool bSuc = ParseToken();
|
|
if (!bSuc)
|
|
{
|
|
AssertMsg(false, "THEN token in response lacked any further info.\n");
|
|
ResponseWarning( "THEN token in response lacked any further info.\n" );
|
|
return;
|
|
}
|
|
|
|
newResponse.m_followup.followup_target = ResponseCopyString(token);
|
|
|
|
bSuc = ParseToken(); // get another token
|
|
if (!bSuc)
|
|
{
|
|
AssertMsg1(false, "THEN token in response had a target '%s', but lacked any further info.\n", newResponse.m_followup.followup_target );
|
|
ResponseWarning( "THEN token in response had a target '%s', but lacked any further info.\n", newResponse.m_followup.followup_target );
|
|
return;
|
|
}
|
|
|
|
newResponse.m_followup.followup_concept = ResponseCopyString( token );
|
|
|
|
|
|
// Okay, this is totally asinine.
|
|
// Because the ParseToken() function will split foo:bar into three tokens
|
|
// (which is reasonable), but we have no safe way to parse the file otherwise
|
|
// because it's all behind an engine interface, it's necessary to parse all
|
|
// the tokens to the end of the line and catenate them, except for the last one
|
|
// which is the delay. That's crap.
|
|
bSuc = ParseToken();
|
|
if (!bSuc)
|
|
{
|
|
AssertMsg(false, "THEN token in response lacked contexts.\n");
|
|
ResponseWarning( "THEN token in response lacked contexts.\n" );
|
|
return;
|
|
}
|
|
|
|
// okay, as long as there is at least one more token, catenate the ones we
|
|
// see onto a temporary buffer. When we're down to the last token, that is
|
|
// the delay.
|
|
char buf[4096];
|
|
buf[0] = '\0';
|
|
while ( TokenWaiting() )
|
|
{
|
|
Q_strncat( buf, token, 4096 );
|
|
bSuc = ParseToken();
|
|
AssertMsg(bSuc, "Token parsing mysteriously failed.");
|
|
}
|
|
|
|
// down here, token is the last token, and buf is everything up to there.
|
|
newResponse.m_followup.followup_contexts = ResponseCopyString( buf );
|
|
|
|
newResponse.m_followup.followup_delay = atof( token );
|
|
}
|
|
|
|
void CResponseSystem::ParseOneResponse( const char *responseGroupName, ResponseGroup& group, ResponseParams *defaultParams )
|
|
{
|
|
ParserResponse &newResponse = group.group[ group.group.AddToTail() ];
|
|
newResponse.weight.SetFloat( 1.0f );
|
|
// inherit from group if appropriate
|
|
if (defaultParams)
|
|
{
|
|
newResponse.params = *defaultParams;
|
|
}
|
|
|
|
ResponseParams *rp = &newResponse.params;
|
|
|
|
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 = ResponseCopyString( token );
|
|
|
|
while ( TokenWaiting() )
|
|
{
|
|
ParseToken();
|
|
|
|
unsigned int hash = RR_HASH( token );
|
|
if ( DispatchParseResponse( token, hash, m_ResponseDispatch, newResponse, group, rp ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ResponseWarning( "response entry '%s' with unknown command '%s'\n", responseGroupName, token );
|
|
}
|
|
|
|
}
|
|
|
|
void CResponseSystem::ParseResponseGroup_Start( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams )
|
|
{
|
|
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 );
|
|
}
|
|
}
|
|
|
|
void CResponseSystem::ParseResponseGroup_PreDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams )
|
|
{
|
|
ParseToken();
|
|
groupResponseParams.flags |= AI_ResponseParams::RG_DELAYBEFORESPEAK;
|
|
groupResponseParams.predelay.FromInterval( ReadInterval( token ) );
|
|
}
|
|
|
|
void CResponseSystem::ParseResponseGroup_NoDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams )
|
|
{
|
|
ParseToken();
|
|
groupResponseParams.flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK;
|
|
groupResponseParams.delay.start = 0;
|
|
groupResponseParams.delay.range = 0;
|
|
}
|
|
|
|
void CResponseSystem::ParseResponseGroup_DefaultDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams )
|
|
{
|
|
groupResponseParams.flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK;
|
|
groupResponseParams.delay.start = AIS_DEF_MIN_DELAY;
|
|
groupResponseParams.delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY );
|
|
}
|
|
|
|
void CResponseSystem::ParseResponseGroup_Delay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams )
|
|
{
|
|
ParseToken();
|
|
groupResponseParams.flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK;
|
|
groupResponseParams.delay.FromInterval( ReadInterval( token ) );
|
|
}
|
|
|
|
void CResponseSystem::ParseResponseGroup_SpeakOnce( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams )
|
|
{
|
|
groupResponseParams.flags |= AI_ResponseParams::RG_SPEAKONCE;
|
|
}
|
|
|
|
void CResponseSystem::ParseResponseGroup_NoScene( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams )
|
|
{
|
|
groupResponseParams.flags |= AI_ResponseParams::RG_DONT_USE_SCENE;
|
|
}
|
|
|
|
void CResponseSystem::ParseResponseGroup_StopOnNonIdle( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams )
|
|
{
|
|
groupResponseParams.flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE;
|
|
}
|
|
|
|
void CResponseSystem::ParseResponseGroup_Odds( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams )
|
|
{
|
|
ParseToken();
|
|
groupResponseParams.flags |= AI_ResponseParams::RG_ODDS;
|
|
groupResponseParams.odds = clamp( atoi( token ), 0, 100 );
|
|
}
|
|
|
|
void CResponseSystem::ParseResponseGroup_RespeakDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams )
|
|
{
|
|
ParseToken();
|
|
groupResponseParams.flags |= AI_ResponseParams::RG_RESPEAKDELAY;
|
|
groupResponseParams.respeakdelay.FromInterval( ReadInterval( token ) );
|
|
}
|
|
|
|
void CResponseSystem::ParseResponseGroup_WeaponDelay( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams )
|
|
{
|
|
ParseToken();
|
|
groupResponseParams.flags |= AI_ResponseParams::RG_WEAPONDELAY;
|
|
groupResponseParams.weapondelay.FromInterval( ReadInterval( token ) );
|
|
}
|
|
|
|
void CResponseSystem::ParseResponseGroup_Soundlevel( char const *responseGroupName, ResponseGroup &newGroup, AI_ResponseParams &groupResponseParams )
|
|
{
|
|
ParseToken();
|
|
groupResponseParams.flags |= AI_ResponseParams::RG_SOUNDLEVEL;
|
|
groupResponseParams.soundlevel = (soundlevel_t)TextToSoundLevel( token );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CResponseSystem::ParseResponse( void )
|
|
{
|
|
AI_ResponseParams groupResponseParams; // default response parameters inherited from single line format for group
|
|
|
|
// Should have groupname at start
|
|
ParseToken();
|
|
char responseGroupName[ 128 ];
|
|
Q_strncpy( responseGroupName, token, sizeof( responseGroupName ) );
|
|
|
|
int slot = m_Responses.Insert( responseGroupName );
|
|
ResponseGroup &newGroup = m_Responses[ slot ];
|
|
|
|
while ( 1 )
|
|
{
|
|
ParseToken();
|
|
|
|
unsigned int hash = RR_HASH( token );
|
|
|
|
// Oops, part of next definition
|
|
if( IsRootCommand( hash ) )
|
|
{
|
|
Unget();
|
|
break;
|
|
}
|
|
|
|
if ( DispatchParseResponseGroup( token, hash, m_ResponseGroupDispatch, responseGroupName, newGroup, groupResponseParams ) )
|
|
{
|
|
continue;
|
|
}
|
|
ParseOneResponse( responseGroupName, newGroup );
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : *criterion -
|
|
//-----------------------------------------------------------------------------
|
|
int CResponseSystem::ParseOneCriterion( const char *criterionName )
|
|
{
|
|
char key[ 128 ];
|
|
char value[ 128 ];
|
|
|
|
Criteria *pNewCriterion = NULL;
|
|
|
|
int idx;
|
|
if ( m_Criteria.Find( criterionName ) != m_Criteria.InvalidIndex() )
|
|
{
|
|
static Criteria dummy;
|
|
pNewCriterion = &dummy;
|
|
|
|
ResponseWarning( "Multiple definitions for criteria '%s' [%d]\n", criterionName, RR_HASH( criterionName ) );
|
|
idx = m_Criteria.InvalidIndex();
|
|
}
|
|
else
|
|
{
|
|
idx = m_Criteria.Insert( criterionName );
|
|
pNewCriterion = &m_Criteria[ idx ];
|
|
}
|
|
|
|
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() )
|
|
{
|
|
pNewCriterion->subcriteria.AddToTail( idx );
|
|
}
|
|
else
|
|
{
|
|
ResponseWarning( "Skipping unrecongized subcriterion '%s' in '%s'\n", token, criterionName );
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
else if ( !Q_stricmp( token, "required" ) )
|
|
{
|
|
pNewCriterion->required = true;
|
|
}
|
|
else if ( !Q_stricmp( token, "weight" ) )
|
|
{
|
|
ParseToken();
|
|
pNewCriterion->weight.SetFloat( (float)atof( token ) );
|
|
}
|
|
else
|
|
{
|
|
Assert( pNewCriterion->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 ) );
|
|
|
|
V_strlower( key );
|
|
pNewCriterion->nameSym = CriteriaSet::ComputeCriteriaSymbol( key );
|
|
pNewCriterion->value = ResponseCopyString( value );
|
|
|
|
gotbody = true;
|
|
}
|
|
}
|
|
|
|
if ( !pNewCriterion->IsSubCriteriaType() )
|
|
{
|
|
ComputeMatcher( pNewCriterion, pNewCriterion->matcher );
|
|
}
|
|
|
|
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 );
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
|
|
void CResponseSystem::ParseRule_MatchOnce( Rule &newRule )
|
|
{
|
|
newRule.m_bMatchOnce = true;
|
|
}
|
|
|
|
void CResponseSystem::ParseRule_ApplyContextToWorld( Rule &newRule )
|
|
{
|
|
newRule.m_bApplyContextToWorld = true;
|
|
}
|
|
|
|
void CResponseSystem::ParseRule_ApplyContext( Rule &newRule )
|
|
{
|
|
ParseToken();
|
|
if ( newRule.GetContext() == NULL )
|
|
{
|
|
newRule.SetContext( token );
|
|
}
|
|
else
|
|
{
|
|
CFmtStrN<1024> newContext( "%s,%s", newRule.GetContext(), token );
|
|
newRule.SetContext( newContext );
|
|
}
|
|
}
|
|
|
|
void CResponseSystem::ParseRule_Response( Rule &newRule )
|
|
{
|
|
// 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
|
|
{
|
|
m_bParseRuleValid = false;
|
|
ResponseWarning( "No such response '%s' for rule '%s'\n", token, m_pParseRuleName );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
void CResponseSystem::ParseRule_ForceWeight( Rule &newRule )
|
|
{
|
|
ParseToken();
|
|
if ( token[0] == 0 )
|
|
{
|
|
// no token followed forceweight?
|
|
ResponseWarning( "Forceweight token in rule '%s' did not specify a numerical weight! Ignoring.\n", m_pParseRuleName );
|
|
}
|
|
else
|
|
{
|
|
newRule.m_nForceWeight = atoi(token);
|
|
if ( newRule.m_nForceWeight == 0 )
|
|
{
|
|
ResponseWarning( "Rule '%s' had forceweight '%s', which doesn't work out to a nonzero number. Ignoring.\n",
|
|
m_pParseRuleName, token );
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
void CResponseSystem::ParseRule_Criteria( Rule &newRule )
|
|
{
|
|
// 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
|
|
{
|
|
m_bParseRuleValid = false;
|
|
ResponseWarning( "No such criterion '%s' for rule '%s'\n", token, m_pParseRuleName );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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 = new Rule;
|
|
|
|
char sz[ 128 ];
|
|
|
|
m_bParseRuleValid = true;
|
|
m_pParseRuleName = ruleName;
|
|
while ( 1 )
|
|
{
|
|
ParseToken();
|
|
if ( !Q_stricmp( token, "}" ) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ( Q_strlen( token ) <= 0 )
|
|
{
|
|
ResponseWarning( "Expecting more tokens in rule '%s'\n", ruleName );
|
|
break;
|
|
}
|
|
|
|
unsigned int hash = RR_HASH( token );
|
|
if ( DispatchParseRule( token, hash, m_RuleDispatch, *newRule ) )
|
|
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 ( m_bParseRuleValid )
|
|
{
|
|
m_RulePartitions.GetDictForRule( this, newRule ).Insert( ruleName, newRule );
|
|
}
|
|
else
|
|
{
|
|
DevMsg( "Discarded rule %s\n", ruleName );
|
|
delete newRule;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// 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;
|
|
char string[1024];
|
|
|
|
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.nameSym = pSrcCriteria->nameSym ;
|
|
dstCriteria.value = ResponseCopyString( 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.nameSym = pSrcSubCriteria->nameSym ;
|
|
dstSubCriteria.value = ResponseCopyString( 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.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 )
|
|
{
|
|
ParserResponse *pSrcResponse = &pSrcResponseGroup->group[iResponse];
|
|
if ( pSrcResponse )
|
|
{
|
|
// Add Response
|
|
ParserResponse dstResponse;
|
|
|
|
dstResponse.weight = pSrcResponse->weight;
|
|
dstResponse.type = pSrcResponse->type;
|
|
dstResponse.value = ResponseCopyString( 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, ResponseRulePartition::tIndex iRule, CResponseSystem *pCustomSystem )
|
|
{
|
|
// Verify data.
|
|
Assert( pSrcRule );
|
|
Assert( pCustomSystem );
|
|
if ( !pSrcRule || !pCustomSystem )
|
|
return;
|
|
|
|
// New rule
|
|
Rule *dstRule = new Rule;
|
|
|
|
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_RulePartitions.GetDictForRule( this, dstRule ).Insert( m_RulePartitions.GetElementName( iRule ), dstRule );
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CResponseSystem::DumpRules()
|
|
{
|
|
for ( ResponseRulePartition::tIndex idx = m_RulePartitions.First() ;
|
|
m_RulePartitions.IsValid(idx) ;
|
|
idx = m_RulePartitions.Next(idx) )
|
|
{
|
|
Msg("%s\n", m_RulePartitions.GetElementName( idx ) );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
//-----------------------------------------------------------------------------
|
|
void CResponseSystem::DumpDictionary( const char *pszName )
|
|
{
|
|
Msg( "\nDictionary: %s\n", pszName );
|
|
|
|
// int nRuleCount = m_Rules.Count();
|
|
// for ( int iRule = 0; iRule < nRuleCount; ++iRule )
|
|
for ( ResponseRulePartition::tIndex idx = m_RulePartitions.First() ;
|
|
m_RulePartitions.IsValid(idx) ;
|
|
idx = m_RulePartitions.Next(idx) )
|
|
{
|
|
Msg(" Rule %d/%d: %s\n", m_RulePartitions.BucketFromIdx(idx), m_RulePartitions.PartFromIdx( idx ), m_RulePartitions.GetElementName( idx ) );
|
|
|
|
Rule *pRule = &m_RulePartitions[idx];
|
|
|
|
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, CriteriaSet::SymbolToStr(pCriteria->nameSym), 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 )
|
|
{
|
|
ParserResponse *pResponse = &pResponseGroup->group[iResponse];
|
|
Msg( " Response %d: %s\n", iResponse, pResponse->value );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CResponseSystem::BuildDispatchTables()
|
|
{
|
|
m_RootCommandHashes.Insert( RR_HASH( "#include" ) );
|
|
m_RootCommandHashes.Insert( RR_HASH( "response" ) );
|
|
m_RootCommandHashes.Insert( RR_HASH( "enumeration" ) );
|
|
m_RootCommandHashes.Insert( RR_HASH( "criterion" ) );
|
|
m_RootCommandHashes.Insert( RR_HASH( "criteria" ) );
|
|
m_RootCommandHashes.Insert( RR_HASH( "rule" ) );
|
|
|
|
m_FileDispatch.Insert( RR_HASH( "#include" ), &CResponseSystem::ParseInclude );
|
|
m_FileDispatch.Insert( RR_HASH( "response" ), &CResponseSystem::ParseResponse );
|
|
m_FileDispatch.Insert( RR_HASH( "criterion" ), &CResponseSystem::ParseCriterion );
|
|
m_FileDispatch.Insert( RR_HASH( "criteria" ), &CResponseSystem::ParseCriterion );
|
|
m_FileDispatch.Insert( RR_HASH( "rule" ), &CResponseSystem::ParseRule );
|
|
m_FileDispatch.Insert( RR_HASH( "enumeration" ), &CResponseSystem::ParseEnumeration );
|
|
|
|
m_RuleDispatch.Insert( RR_HASH( "matchonce" ), &CResponseSystem::ParseRule_MatchOnce );
|
|
m_RuleDispatch.Insert( RR_HASH( "applycontexttoworld" ), &CResponseSystem::ParseRule_ApplyContextToWorld );
|
|
m_RuleDispatch.Insert( RR_HASH( "applycontext" ), &CResponseSystem::ParseRule_ApplyContext );
|
|
m_RuleDispatch.Insert( RR_HASH( "response" ), &CResponseSystem::ParseRule_Response );
|
|
// m_RuleDispatch.Insert( RR_HASH( "forceweight" ), &CResponseSystem::ParseRule_ForceWeight );
|
|
m_RuleDispatch.Insert( RR_HASH( "criteria" ), &CResponseSystem::ParseRule_Criteria );
|
|
m_RuleDispatch.Insert( RR_HASH( "criterion" ), &CResponseSystem::ParseRule_Criteria );
|
|
|
|
|
|
m_ResponseDispatch.Insert( RR_HASH( "weight" ), &CResponseSystem::ParseResponse_Weight );
|
|
m_ResponseDispatch.Insert( RR_HASH( "predelay" ), &CResponseSystem::ParseResponse_PreDelay );
|
|
m_ResponseDispatch.Insert( RR_HASH( "nodelay" ), &CResponseSystem::ParseResponse_NoDelay );
|
|
m_ResponseDispatch.Insert( RR_HASH( "defaultdelay" ), &CResponseSystem::ParseResponse_DefaultDelay );
|
|
m_ResponseDispatch.Insert( RR_HASH( "delay" ), &CResponseSystem::ParseResponse_Delay );
|
|
m_ResponseDispatch.Insert( RR_HASH( "speakonce" ), &CResponseSystem::ParseResponse_SpeakOnce );
|
|
m_ResponseDispatch.Insert( RR_HASH( "noscene" ), &CResponseSystem::ParseResponse_NoScene );
|
|
m_ResponseDispatch.Insert( RR_HASH( "stop_on_nonidle" ), &CResponseSystem::ParseResponse_StopOnNonIdle );
|
|
m_ResponseDispatch.Insert( RR_HASH( "odds" ), &CResponseSystem::ParseResponse_Odds );
|
|
m_ResponseDispatch.Insert( RR_HASH( "respeakdelay" ), &CResponseSystem::ParseResponse_RespeakDelay );
|
|
m_ResponseDispatch.Insert( RR_HASH( "weapondelay" ), &CResponseSystem::ParseResponse_WeaponDelay );
|
|
m_ResponseDispatch.Insert( RR_HASH( "soundlevel" ), &CResponseSystem::ParseResponse_Soundlevel );
|
|
m_ResponseDispatch.Insert( RR_HASH( "displayfirst" ), &CResponseSystem::ParseResponse_DisplayFirst );
|
|
m_ResponseDispatch.Insert( RR_HASH( "displaylast" ), &CResponseSystem::ParseResponse_DisplayLast );
|
|
m_ResponseDispatch.Insert( RR_HASH( "fire" ), &CResponseSystem::ParseResponse_Fire );
|
|
m_ResponseDispatch.Insert( RR_HASH( "then" ), &CResponseSystem::ParseResponse_Then );
|
|
|
|
m_ResponseGroupDispatch.Insert( RR_HASH( "{" ), &CResponseSystem::ParseResponseGroup_Start );
|
|
m_ResponseGroupDispatch.Insert( RR_HASH( "predelay" ), &CResponseSystem::ParseResponseGroup_PreDelay );
|
|
m_ResponseGroupDispatch.Insert( RR_HASH( "nodelay" ), &CResponseSystem::ParseResponseGroup_NoDelay );
|
|
m_ResponseGroupDispatch.Insert( RR_HASH( "defaultdelay" ), &CResponseSystem::ParseResponseGroup_DefaultDelay );
|
|
m_ResponseGroupDispatch.Insert( RR_HASH( "delay" ), &CResponseSystem::ParseResponseGroup_Delay );
|
|
m_ResponseGroupDispatch.Insert( RR_HASH( "speakonce" ), &CResponseSystem::ParseResponseGroup_SpeakOnce );
|
|
m_ResponseGroupDispatch.Insert( RR_HASH( "noscene" ), &CResponseSystem::ParseResponseGroup_NoScene );
|
|
m_ResponseGroupDispatch.Insert( RR_HASH( "stop_on_nonidle" ), &CResponseSystem::ParseResponseGroup_StopOnNonIdle );
|
|
m_ResponseGroupDispatch.Insert( RR_HASH( "odds" ), &CResponseSystem::ParseResponseGroup_Odds );
|
|
m_ResponseGroupDispatch.Insert( RR_HASH( "respeakdelay" ), &CResponseSystem::ParseResponseGroup_RespeakDelay );
|
|
m_ResponseGroupDispatch.Insert( RR_HASH( "weapondelay" ), &CResponseSystem::ParseResponseGroup_WeaponDelay );
|
|
m_ResponseGroupDispatch.Insert( RR_HASH( "soundlevel" ), &CResponseSystem::ParseResponseGroup_Soundlevel );
|
|
}
|
|
|
|
bool CResponseSystem::Dispatch( char const *pToken, unsigned int uiHash, CResponseSystem::DispatchMap_t &rMap )
|
|
{
|
|
int slot = rMap.Find( uiHash );
|
|
if ( slot != rMap.InvalidIndex() )
|
|
{
|
|
CResponseSystem::pfnResponseDispatch dispatch = rMap[ slot ];
|
|
(this->*dispatch)();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CResponseSystem::DispatchParseRule( char const *pToken, unsigned int uiHash, ParseRuleDispatchMap_t &rMap, Rule &newRule )
|
|
{
|
|
int slot = rMap.Find( uiHash );
|
|
if ( slot != rMap.InvalidIndex() )
|
|
{
|
|
CResponseSystem::pfnParseRuleDispatch dispatch = rMap[ slot ];
|
|
(this->*dispatch)( newRule );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CResponseSystem::DispatchParseResponse( char const *pToken, unsigned int uiHash, ParseResponseDispatchMap_t &rMap, ParserResponse &newResponse, ResponseGroup& group, AI_ResponseParams *rp )
|
|
{
|
|
int slot = rMap.Find( uiHash );
|
|
if ( slot != rMap.InvalidIndex() )
|
|
{
|
|
CResponseSystem::pfnParseResponseDispatch dispatch = rMap[ slot ];
|
|
(this->*dispatch)( newResponse, group, rp );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CResponseSystem::DispatchParseResponseGroup( char const *pToken, unsigned int uiHash, ParseResponseGroupDispatchMap_t &rMap, char const *responseGroupName, ResponseGroup& newGroup, AI_ResponseParams &groupResponseParams )
|
|
{
|
|
int slot = rMap.Find( uiHash );
|
|
if ( slot != rMap.InvalidIndex() )
|
|
{
|
|
CResponseSystem::pfnParseResponseGroupDispatch dispatch = rMap[ slot ];
|
|
(this->*dispatch)( responseGroupName, newGroup, groupResponseParams );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
unsigned int ResponseRulePartition::GetBucketForSpeakerAndConcept( const char *pszSpeaker, const char *pszConcept, const char *pszSubject )
|
|
{
|
|
// make sure is a power of two
|
|
COMPILE_TIME_ASSERT( ( N_RESPONSE_PARTITIONS & ( N_RESPONSE_PARTITIONS - 1 ) ) == 0 );
|
|
|
|
// hash together the speaker and concept strings, and mask off by the bucket mask
|
|
unsigned hashSpeaker = 0; // pszSpeaker ? HashStringCaseless( pszSpeaker ) : 0;
|
|
unsigned hashConcept = pszConcept ? HashStringCaseless( pszConcept ) : 0;
|
|
unsigned hashSubject = pszSubject ? HashStringCaseless( pszSubject ) : 0;
|
|
unsigned hashBrowns = ( ( hashSubject >> 3 ) ^ (hashSpeaker >> 1) ^ hashConcept ) & ( N_RESPONSE_PARTITIONS - 1 );
|
|
return hashBrowns;
|
|
}
|
|
|
|
const char *Rule::GetValueForRuleCriterionByName( CResponseSystem * RESTRICT pSystem, const CUtlSymbol &pCritNameSym )
|
|
{
|
|
const char * retval = NULL;
|
|
// for each rule criterion...
|
|
for ( int i = 0 ; i < m_Criteria.Count() ; ++i )
|
|
{
|
|
retval = RecursiveGetValueForRuleCriterionByName( pSystem, &pSystem->m_Criteria[m_Criteria[i]], pCritNameSym );
|
|
if ( retval != NULL )
|
|
{
|
|
// we found a result, early out
|
|
break;
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
const Criteria *Rule::GetPointerForRuleCriterionByName( CResponseSystem *pSystem, const CUtlSymbol &pCritNameSym )
|
|
{
|
|
const Criteria * retval = NULL;
|
|
// for each rule criterion...
|
|
for ( int i = 0 ; i < m_Criteria.Count() ; ++i )
|
|
{
|
|
retval = RecursiveGetPointerForRuleCriterionByName( pSystem, &pSystem->m_Criteria[m_Criteria[i]], pCritNameSym );
|
|
if ( retval != NULL )
|
|
{
|
|
// we found a result, early out
|
|
break;
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
const char *Rule::RecursiveGetValueForRuleCriterionByName( CResponseSystem * RESTRICT pSystem,
|
|
const Criteria * RESTRICT pCrit, const CUtlSymbol &pCritNameSym )
|
|
{
|
|
Assert( pCrit );
|
|
if ( !pCrit ) return NULL;
|
|
if ( pCrit->IsSubCriteriaType() )
|
|
{
|
|
// test each of the children (depth first)
|
|
const char *pRet = NULL;
|
|
for ( int i = 0 ; i < pCrit->subcriteria.Count() ; ++i )
|
|
{
|
|
pRet = RecursiveGetValueForRuleCriterionByName( pSystem, &pSystem->m_Criteria[pCrit->subcriteria[i]], pCritNameSym );
|
|
if ( pRet ) // if found something, early out
|
|
return pRet;
|
|
}
|
|
}
|
|
else // leaf criterion
|
|
{
|
|
if ( pCrit->nameSym == pCritNameSym )
|
|
{
|
|
return pCrit->value;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
const Criteria *Rule::RecursiveGetPointerForRuleCriterionByName( CResponseSystem *pSystem, const Criteria *pCrit, const CUtlSymbol &pCritNameSym )
|
|
{
|
|
Assert( pCrit );
|
|
if ( !pCrit ) return NULL;
|
|
if ( pCrit->IsSubCriteriaType() )
|
|
{
|
|
// test each of the children (depth first)
|
|
const Criteria *pRet = NULL;
|
|
for ( int i = 0 ; i < pCrit->subcriteria.Count() ; ++i )
|
|
{
|
|
pRet = RecursiveGetPointerForRuleCriterionByName( pSystem, &pSystem->m_Criteria[pCrit->subcriteria[i]], pCritNameSym );
|
|
if ( pRet ) // if found something, early out
|
|
return pRet;
|
|
}
|
|
}
|
|
else // leaf criterion
|
|
{
|
|
if ( pCrit->nameSym == pCritNameSym )
|
|
{
|
|
return pCrit;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
#if RESPONSE_RULES_DEBUG_ENABLED
|
|
static void CC_RR_Debug_ResponseConcept_Exclude( const CCommand &args )
|
|
{
|
|
// shouldn't use this extern elsewhere -- it's meant to be a hidden
|
|
// implementation detail
|
|
extern CRR_ConceptSymbolTable *g_pRRConceptTable;
|
|
Assert( g_pRRConceptTable );
|
|
if ( !g_pRRConceptTable ) return;
|
|
|
|
|
|
// different things for different argument lengths
|
|
switch ( args.ArgC() )
|
|
{
|
|
case 0:
|
|
{
|
|
AssertMsg( args.ArgC() > 0, "WTF error in ccommand parsing: zero arguments!\n" );
|
|
return;
|
|
}
|
|
case 1:
|
|
{
|
|
// print usage info
|
|
Msg("Usage: rr_debugresponseconcept_exclude Concept1 Concept2 Concept3...\n");
|
|
Msg("\tseparate multiple concepts with spaces.\n");
|
|
Msg("\tcall with no arguments to see this message and a list of current excludes.\n");
|
|
Msg("\tto reset the exclude list, type \"rr_debugresponseconcept_exclude !\"\n");
|
|
|
|
// print current excludes
|
|
Msg("\nCurrent exclude list:\n");
|
|
if ( !CResponseSystem::m_DebugExcludeList.IsValidIndex( CResponseSystem::m_DebugExcludeList.Head() ) )
|
|
{
|
|
Msg("\t<none>\n");
|
|
}
|
|
else
|
|
{
|
|
CResponseSystem::ExcludeList_t::IndexLocalType_t i;
|
|
for ( i = CResponseSystem::m_DebugExcludeList.Head() ;
|
|
CResponseSystem::m_DebugExcludeList.IsValidIndex(i) ;
|
|
i = CResponseSystem::m_DebugExcludeList.Next(i) )
|
|
{
|
|
Msg( "\t%s\n", CResponseSystem::m_DebugExcludeList[i].GetStringConcept() );
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
case 2:
|
|
// deal with the erase operator
|
|
if ( args[1][0] == '!' )
|
|
{
|
|
CResponseSystem::m_DebugExcludeList.Purge();
|
|
Msg( "Exclude list emptied.\n" );
|
|
return;
|
|
}
|
|
// else, FALL THROUGH:
|
|
default:
|
|
// add each arg to the exclude list
|
|
for ( int i = 1 ; i < args.ArgC() ; ++i )
|
|
{
|
|
if ( !g_pRRConceptTable->Find(args[i]).IsValid() )
|
|
{
|
|
Msg( "\t'%s' is not a known concept (adding it anyway)\n", args[i] );
|
|
}
|
|
CRR_Concept concept( args[i] );
|
|
CResponseSystem::m_DebugExcludeList.AddToTail( concept );
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
#if RR_DUMPHASHINFO_ENABLED
|
|
static void CC_RR_DumpHashInfo( const CCommand &args )
|
|
{
|
|
defaultresponsesytem.m_InstancedSystems[0]->m_RulePartitions.PrintBucketInfo( defaultresponsesytem.m_InstancedSystems[0] );
|
|
}
|
|
static ConCommand rr_dumphashinfo( "rr_dumphashinfo", CC_RR_DumpHashInfo, "Statistics on primary hash bucketing of response rule partitions");
|
|
|
|
void ResponseRulePartition::PrintBucketInfo( CResponseSystem *pSys )
|
|
{
|
|
struct bucktuple_t
|
|
{
|
|
int nBucket;
|
|
int nCount;
|
|
bucktuple_t() : nBucket(-1), nCount(-1) {};
|
|
bucktuple_t( int bucket, int count ) : nBucket(bucket), nCount(count) {};
|
|
|
|
static int __cdecl SortCompare( const bucktuple_t * a, const bucktuple_t * b )
|
|
{
|
|
return a->nCount - b->nCount;
|
|
}
|
|
};
|
|
|
|
CUtlVector<bucktuple_t> infos( N_RESPONSE_PARTITIONS, N_RESPONSE_PARTITIONS );
|
|
|
|
float nAverage = 0;
|
|
for ( int i = 0 ; i < N_RESPONSE_PARTITIONS ; ++i )
|
|
{
|
|
int count = m_RuleParts[i].Count();
|
|
infos.AddToTail( bucktuple_t( i, count ) );
|
|
nAverage += count;
|
|
}
|
|
nAverage /= N_RESPONSE_PARTITIONS;
|
|
infos.Sort( bucktuple_t::SortCompare );
|
|
Msg( "%d buckets, %d total, %.2f average size\n", N_RESPONSE_PARTITIONS, Count(), nAverage );
|
|
Msg( "8 shortest buckets:\n" );
|
|
for ( int i = 0 ; i < 8 ; ++i )
|
|
{
|
|
Msg("\t%d: %d\n", infos[i].nBucket, infos[i].nCount );
|
|
}
|
|
Msg( "8 longest buckets:\n" );
|
|
for ( int i = infos.Count() - 1 ; i >= infos.Count() - 9 ; --i )
|
|
{
|
|
Msg("\t%d: %d\n", infos[i].nBucket, infos[i].nCount );
|
|
}
|
|
int nempty = 0;
|
|
for ( nempty = 0 ; nempty < infos.Count() ; ++nempty )
|
|
{
|
|
if ( infos[nempty].nCount != 0 )
|
|
break;
|
|
}
|
|
Msg( "%d empty buckets\n", nempty );
|
|
|
|
/*
|
|
Msg( " Contents of longest bucket\nwho\tconcept\n" );
|
|
tRuleDict &bucket = m_RuleParts[infos[infos.Count()-1].nBucket];
|
|
for ( tRuleDict::IndexType_t i = bucket.FirstInorder(); bucket.IsValidIndex(i); i = bucket.NextInorder(i) )
|
|
{
|
|
Rule &rule = bucket.Element(i) ;
|
|
Msg("%s\t%s\n", rule.GetValueForRuleCriterionByName( pSys, "who" ), rule.GetValueForRuleCriterionByName( pSys, CriteriaSet::ComputeCriteriaSymbol("concept") ) );
|
|
}
|
|
*/
|
|
}
|
|
#endif
|