Counter Strike : Global Offensive Source Code
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

//========= 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