|
|
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "ai_responsesystem.h"
#include "igamesystem.h"
#include "ai_criteria.h"
#include <keyvalues.h>
#include "filesystem.h"
#include "utldict.h"
#include "ai_speech.h"
#include "tier0/icommandline.h"
#include <ctype.h>
#include "sceneentity.h"
#include "isaverestore.h"
#include "utlbuffer.h"
#include "stringpool.h"
#include "fmtstr.h"
#include "multiplay_gamerules.h"
#include "characterset.h"
#include "responserules/response_host_interface.h"
#include "responserules/response_types_internal.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
using namespace ResponseRules;
extern 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." );
extern 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.");
extern ConVar rr_dumpresponses; // ( "rr_dumpresponses", "0", FCVAR_NONE, "Dump all response_rules.txt and rules (requires restart)" );
extern ConVar rr_debugresponseconcept; // ( "rr_debugresponseconcept", "", FCVAR_NONE, "If set, rr_debugresponses will print only responses testing for the specified concept" );
static characterset_t g_BreakSetIncludingColons;
// Simple class to initialize breakset
class CBreakInit { public: CBreakInit() { CharacterSetBuild( &g_BreakSetIncludingColons, "{}()':" ); } } g_BreakInit;
inline char rr_tolower( char c ) { if ( c >= 'A' && c <= 'Z' ) return c - 'A' + 'a'; return c; } // BUG BUG: Note that this function doesn't check for data overruns!!!
// Also, this function lowercases the token as it parses!!!
inline const char *RR_Parse(const char *data, char *token ) { unsigned char c; int len; characterset_t *breaks = &g_BreakSetIncludingColons; len = 0; token[0] = 0;
if (!data) return NULL;
// skip whitespace
skipwhite: while ( (c = *data) <= ' ') { if (c == 0) return NULL; // end of file;
data++; }
// skip // comments
if (c=='/' && data[1] == '/') { while (*data && *data != '\n') data++; goto skipwhite; }
// handle quoted strings specially
if (c == '\"') { data++; while (1) { c = rr_tolower( *data++ ); if (c=='\"' || !c) { token[len] = 0; return data; } token[len] = c; len++; } }
// parse single characters
if ( IN_CHARACTERSET( *breaks, c ) ) { token[len] = c; len++; token[len] = 0; return data+1; }
// parse a regular word
do { token[len] = rr_tolower( c ); data++; len++; c = rr_tolower( *data ); if ( IN_CHARACTERSET( *breaks, c ) ) break; } while (c>32);
token[len] = 0; return data; }
namespace ResponseRules { extern const char *ResponseCopyString( const char *in ); }
// Host functions required by the ResponseRules::IEngineEmulator interface
class CResponseRulesToEngineInterface : public ResponseRules::IEngineEmulator { /// Given an input text buffer data pointer, parses a single token into the variable token and returns the new
/// reading position
virtual const char *ParseFile( const char *data, char *token, int maxlen ) { NOTE_UNUSED( maxlen ); return RR_Parse( data, token ); }
/// Return a pointer to an IFileSystem we can use to read and process scripts.
virtual IFileSystem *GetFilesystem() { return filesystem; }
/// Return a pointer to an instance of an IUniformRandomStream
virtual IUniformRandomStream *GetRandomStream() { return random; }
/// Return a pointer to a tier0 ICommandLine
virtual ICommandLine *GetCommandLine() { return CommandLine(); }
/// Emulates the server's UTIL_LoadFileForMe
virtual byte *LoadFileForMe( const char *filename, int *pLength ) { return UTIL_LoadFileForMe( filename, pLength ); }
/// Emulates the server's UTIL_FreeFile
virtual void FreeFile( byte *buffer ) { return UTIL_FreeFile( buffer ); }
};
CResponseRulesToEngineInterface g_ResponseRulesEngineWrapper; IEngineEmulator *IEngineEmulator::s_pSingleton = &g_ResponseRulesEngineWrapper;
BEGIN_SIMPLE_DATADESC( ParserResponse ) // DEFINE_FIELD( type, FIELD_INTEGER ),
// DEFINE_ARRAY( value, FIELD_CHARACTER ),
// DEFINE_FIELD( weight, FIELD_FLOAT ),
DEFINE_FIELD( depletioncount, FIELD_CHARACTER ), // DEFINE_FIELD( first, FIELD_BOOLEAN ),
// DEFINE_FIELD( last, FIELD_BOOLEAN ),
END_DATADESC()
BEGIN_SIMPLE_DATADESC( ResponseGroup ) // DEFINE_FIELD( group, FIELD_UTLVECTOR ),
// DEFINE_FIELD( rp, FIELD_EMBEDDED ),
// DEFINE_FIELD( m_bDepleteBeforeRepeat, FIELD_BOOLEAN ),
DEFINE_FIELD( m_nDepletionCount, FIELD_CHARACTER ), // DEFINE_FIELD( m_bHasFirst, FIELD_BOOLEAN ),
// DEFINE_FIELD( m_bHasLast, FIELD_BOOLEAN ),
// DEFINE_FIELD( m_bSequential, FIELD_BOOLEAN ),
// DEFINE_FIELD( m_bNoRepeat, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), DEFINE_FIELD( m_nCurrentIndex, FIELD_CHARACTER ), END_DATADESC()
/// Add some game-specific code to the basic response system
/// (eg, the scene precacher, which requires the client and server
/// to work)
class CGameResponseSystem : public CResponseSystem { public: CGameResponseSystem();
virtual void Precache(); virtual void PrecacheResponses( bool bEnable ) { m_bPrecache = bEnable; } bool ShouldPrecache() { return m_bPrecache; }
protected: bool m_bPrecache; };
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CGameResponseSystem::CGameResponseSystem() : m_bPrecache(true) {};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static void TouchFile( char const *pchFileName ) { IEngineEmulator::Get()->GetFilesystem()->Size( pchFileName ); }
void CGameResponseSystem::Precache() { bool bTouchFiles = CommandLine()->FindParm( "-makereslists" ) != 0;
// enumerate and mark all the scripts so we know they're referenced
for ( int i = 0; i < (int)m_Responses.Count(); i++ ) { ResponseGroup &group = m_Responses[i];
for ( int j = 0; j < group.group.Count(); j++) { ParserResponse &response = group.group[j];
switch ( response.type ) { default: break; case RESPONSE_SCENE: { // fixup $gender references
char file[_MAX_PATH]; Q_strncpy( file, response.value, sizeof(file) ); char *gender = strstr( file, "$gender" ); if ( gender ) { // replace with male & female
const char *postGender = gender + strlen("$gender"); *gender = 0; char genderFile[_MAX_PATH]; // male
Q_snprintf( genderFile, sizeof(genderFile), "%smale%s", file, postGender);
PrecacheInstancedScene( genderFile ); if ( bTouchFiles ) { TouchFile( genderFile ); }
Q_snprintf( genderFile, sizeof(genderFile), "%sfemale%s", file, postGender);
PrecacheInstancedScene( genderFile ); if ( bTouchFiles ) { TouchFile( genderFile ); } } else { PrecacheInstancedScene( file ); if ( bTouchFiles ) { TouchFile( file ); } } } break; case RESPONSE_SPEAK: { CBaseEntity::PrecacheScriptSound( response.value ); } break; } } } }
//-----------------------------------------------------------------------------
// Purpose: A special purpose response system associated with a custom entity
//-----------------------------------------------------------------------------
class CInstancedResponseSystem : public CGameResponseSystem { typedef CGameResponseSystem BaseClass;
public: CInstancedResponseSystem( const char *scriptfile ) : m_pszScriptFile( 0 ) { Assert( scriptfile );
int len = Q_strlen( scriptfile ) + 1; m_pszScriptFile = new char[ len ]; Assert( m_pszScriptFile ); Q_strncpy( m_pszScriptFile, scriptfile, len ); }
~CInstancedResponseSystem() { delete[] m_pszScriptFile; } virtual const char *GetScriptFile( void ) { Assert( m_pszScriptFile ); return m_pszScriptFile; }
// CAutoGameSystem
virtual bool Init() { const char *basescript = GetScriptFile(); LoadRuleSet( basescript ); return true; }
virtual void LevelInitPostEntity() { ResetResponseGroups(); }
virtual void Release() { Clear(); delete this; } private:
char *m_pszScriptFile; };
//-----------------------------------------------------------------------------
// Purpose: The default response system for expressive AIs
//-----------------------------------------------------------------------------
class CDefaultResponseSystem : public CGameResponseSystem, public CAutoGameSystem { typedef CAutoGameSystem BaseClass;
public: CDefaultResponseSystem() : CAutoGameSystem( "CDefaultResponseSystem" ) { }
virtual const char *GetScriptFile( void ) { return "scripts/talker/response_rules.txt"; }
// CAutoServerSystem
virtual bool Init(); virtual void Shutdown();
virtual void LevelInitPostEntity() { }
virtual void Release() { Assert( 0 ); }
void AddInstancedResponseSystem( const char *scriptfile, CInstancedResponseSystem *sys ) { m_InstancedSystems.Insert( scriptfile, sys ); }
CInstancedResponseSystem *FindResponseSystem( const char *scriptfile ) { int idx = m_InstancedSystems.Find( scriptfile ); if ( idx == m_InstancedSystems.InvalidIndex() ) return NULL; return m_InstancedSystems[ idx ]; }
IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ) { COM_TimestampedLog( "PrecacheCustomResponseSystem %s - Start", scriptfile ); CInstancedResponseSystem *sys = ( CInstancedResponseSystem * )FindResponseSystem( scriptfile ); if ( !sys ) { sys = new CInstancedResponseSystem( scriptfile ); if ( !sys ) { Error( "Failed to load response system data from %s", scriptfile ); }
if ( !sys->Init() ) { Error( "CInstancedResponseSystem: Failed to init response system from %s!", scriptfile ); }
AddInstancedResponseSystem( scriptfile, sys ); }
sys->Precache();
COM_TimestampedLog( "PrecacheCustomResponseSystem %s - Finish", scriptfile );
return ( IResponseSystem * )sys; }
IResponseSystem *BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ); void DestroyCustomResponseSystems();
virtual void LevelInitPreEntity() { // This will precache the default system
// All user installed systems are init'd by PrecacheCustomResponseSystem which will call sys->Precache() on the ones being used
// FIXME: This is SLOW the first time you run the engine (can take 3 - 10 seconds!!!)
if ( ShouldPrecache() ) { Precache(); }
ResetResponseGroups(); }
void ReloadAllResponseSystems() { Clear(); Init();
int c = m_InstancedSystems.Count(); for ( int i = c - 1 ; i >= 0; i-- ) { CInstancedResponseSystem *sys = m_InstancedSystems[ i ]; if ( !IsCustomManagable() ) { sys->Clear(); sys->Init(); } else { // Custom reponse rules will manage/reload themselves - remove them.
m_InstancedSystems.RemoveAt( i ); } }
// precache sounds in case we added new ones
Precache();
}
private:
void ClearInstanced() { int c = m_InstancedSystems.Count(); for ( int i = c - 1 ; i >= 0; i-- ) { CInstancedResponseSystem *sys = m_InstancedSystems[ i ]; sys->Release(); } m_InstancedSystems.RemoveAll(); }
CUtlDict< CInstancedResponseSystem *, int > m_InstancedSystems; friend void CC_RR_DumpHashInfo( const CCommand &args ); };
IResponseSystem *CDefaultResponseSystem::BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ) { // Create a instanced response system.
CInstancedResponseSystem *pCustomSystem = new CInstancedResponseSystem( pszCustomName ); if ( !pCustomSystem ) { Error( "BuildCustomResponseSystemGivenCriterea: Failed to create custom response system %s!", pszCustomName ); }
pCustomSystem->Clear();
// Copy the relevant rules and data.
/*
int nRuleCount = m_Rules.Count(); for ( int iRule = 0; iRule < nRuleCount; ++iRule ) */ for ( ResponseRulePartition::tIndex iIdx = m_RulePartitions.First() ; m_RulePartitions.IsValid(iIdx) ; iIdx = m_RulePartitions.Next( iIdx ) ) { Rule *pRule = &m_RulePartitions[iIdx]; if ( pRule ) { float flScore = 0.0f;
int nCriteriaCount = pRule->m_Criteria.Count(); for ( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria ) { int iRuleCriteria = pRule->m_Criteria[iCriteria];
flScore += LookForCriteria( criteriaSet, iRuleCriteria ); if ( flScore >= flCriteriaScore ) { CopyRuleFrom( pRule, iIdx, pCustomSystem ); break; } } } }
// Set as a custom response system.
m_bCustomManagable = true; AddInstancedResponseSystem( pszCustomName, pCustomSystem );
// pCustomSystem->DumpDictionary( pszCustomName );
return pCustomSystem; }
void CDefaultResponseSystem::DestroyCustomResponseSystems() { ClearInstanced(); }
static CDefaultResponseSystem defaultresponsesytem; IResponseSystem *g_pResponseSystem = &defaultresponsesytem;
CON_COMMAND_F( rr_reloadresponsesystems, "Reload all response system scripts.", FCVAR_CHEAT ) { defaultresponsesytem.ReloadAllResponseSystems(); }
static short RESPONSESYSTEM_SAVE_RESTORE_VERSION = 1;
// note: this won't save/restore settings from instanced response systems. Could add that with a CDefSaveRestoreOps implementation if needed
//
class CDefaultResponseSystemSaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler { public: const char *GetBlockName() { return "ResponseSystem"; }
void WriteSaveHeaders( ISave *pSave ) { pSave->WriteShort( &RESPONSESYSTEM_SAVE_RESTORE_VERSION ); }
void ReadRestoreHeaders( IRestore *pRestore ) { // No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so.
short version; pRestore->ReadShort( &version ); m_fDoLoad = ( version == RESPONSESYSTEM_SAVE_RESTORE_VERSION ); }
void Save( ISave *pSave ) { CDefaultResponseSystem& rs = defaultresponsesytem;
int count = rs.m_Responses.Count(); pSave->WriteInt( &count ); for ( int i = 0; i < count; ++i ) { pSave->StartBlock( "ResponseGroup" );
pSave->WriteString( rs.m_Responses.GetElementName( i ) ); const ResponseGroup *group = &rs.m_Responses[ i ]; pSave->WriteAll( group );
short groupCount = group->group.Count(); pSave->WriteShort( &groupCount ); for ( int j = 0; j < groupCount; ++j ) { const ParserResponse *response = &group->group[ j ]; pSave->StartBlock( "Response" ); pSave->WriteString( response->value ); pSave->WriteAll( response ); pSave->EndBlock(); }
pSave->EndBlock(); } }
void Restore( IRestore *pRestore, bool createPlayers ) { if ( !m_fDoLoad ) return;
CDefaultResponseSystem& rs = defaultresponsesytem;
int count = pRestore->ReadInt(); for ( int i = 0; i < count; ++i ) { char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF]; pRestore->StartBlock( szResponseGroupBlockName ); if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) ) {
char groupname[ 256 ]; pRestore->ReadString( groupname, sizeof( groupname ), 0 );
// Try and find it
int idx = rs.m_Responses.Find( groupname ); if ( idx != rs.m_Responses.InvalidIndex() ) { ResponseGroup *group = &rs.m_Responses[ idx ]; pRestore->ReadAll( group );
short groupCount = pRestore->ReadShort(); for ( int j = 0; j < groupCount; ++j ) { char szResponseBlockName[SIZE_BLOCK_NAME_BUF];
char responsename[ 256 ]; pRestore->StartBlock( szResponseBlockName ); if ( !Q_stricmp( szResponseBlockName, "Response" ) ) { pRestore->ReadString( responsename, sizeof( responsename ), 0 );
// Find it by name
int ri; for ( ri = 0; ri < group->group.Count(); ++ri ) { ParserResponse *response = &group->group[ ri ]; if ( !Q_stricmp( response->value, responsename ) ) { break; } }
if ( ri < group->group.Count() ) { ParserResponse *response = &group->group[ ri ]; pRestore->ReadAll( response ); } }
pRestore->EndBlock(); } } }
pRestore->EndBlock(); } } private:
bool m_fDoLoad;
} g_DefaultResponseSystemSaveRestoreBlockHandler;
ISaveRestoreBlockHandler *GetDefaultResponseSystemSaveRestoreBlockHandler() { return &g_DefaultResponseSystemSaveRestoreBlockHandler; }
//-----------------------------------------------------------------------------
// CResponseSystemSaveRestoreOps
//
// Purpose: Handles save and load for instanced response systems...
//
// BUGBUG: This will save the same response system to file multiple times for "shared" response systems and
// therefore it'll restore the same data onto the same pointer N times on reload (probably benign for now, but we could
// write code to save/restore the instanced ones by filename in the block handler above maybe?
//-----------------------------------------------------------------------------
class CResponseSystemSaveRestoreOps : public CDefSaveRestoreOps { public:
virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave ) { CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField; if ( !pRS || pRS == &defaultresponsesytem ) return;
int count = pRS->m_Responses.Count(); pSave->WriteInt( &count ); for ( int i = 0; i < count; ++i ) { pSave->StartBlock( "ResponseGroup" );
pSave->WriteString( pRS->m_Responses.GetElementName( i ) ); const ResponseGroup *group = &pRS->m_Responses[ i ]; pSave->WriteAll( group );
short groupCount = group->group.Count(); pSave->WriteShort( &groupCount ); for ( int j = 0; j < groupCount; ++j ) { const ParserResponse *response = &group->group[ j ]; pSave->StartBlock( "Response" ); pSave->WriteString( response->value ); pSave->WriteAll( response ); pSave->EndBlock(); }
pSave->EndBlock(); } }
virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore ) { CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField; if ( !pRS || pRS == &defaultresponsesytem ) return;
int count = pRestore->ReadInt(); for ( int i = 0; i < count; ++i ) { char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF]; pRestore->StartBlock( szResponseGroupBlockName ); if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) ) {
char groupname[ 256 ]; pRestore->ReadString( groupname, sizeof( groupname ), 0 );
// Try and find it
int idx = pRS->m_Responses.Find( groupname ); if ( idx != pRS->m_Responses.InvalidIndex() ) { ResponseGroup *group = &pRS->m_Responses[ idx ]; pRestore->ReadAll( group );
short groupCount = pRestore->ReadShort(); for ( int j = 0; j < groupCount; ++j ) { char szResponseBlockName[SIZE_BLOCK_NAME_BUF];
char responsename[ 256 ]; pRestore->StartBlock( szResponseBlockName ); if ( !Q_stricmp( szResponseBlockName, "Response" ) ) { pRestore->ReadString( responsename, sizeof( responsename ), 0 );
// Find it by name
int ri; for ( ri = 0; ri < group->group.Count(); ++ri ) { ParserResponse *response = &group->group[ ri ]; if ( !Q_stricmp( response->value, responsename ) ) { break; } }
if ( ri < group->group.Count() ) { ParserResponse *response = &group->group[ ri ]; pRestore->ReadAll( response ); } }
pRestore->EndBlock(); } } }
pRestore->EndBlock(); } }
} g_ResponseSystemSaveRestoreOps;
ISaveRestoreOps *responseSystemSaveRestoreOps = &g_ResponseSystemSaveRestoreOps;
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CDefaultResponseSystem::Init() { /*
Warning( "sizeof( Response ) == %d\n", sizeof( Response ) ); Warning( "sizeof( ResponseGroup ) == %d\n", sizeof( ResponseGroup ) ); Warning( "sizeof( Criteria ) == %d\n", sizeof( Criteria ) ); Warning( "sizeof( AI_ResponseParams ) == %d\n", sizeof( AI_ResponseParams ) ); */ const char *basescript = GetScriptFile();
LoadRuleSet( basescript );
return true; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CDefaultResponseSystem::Shutdown() { // Wipe instanced versions
ClearInstanced();
// Clear outselves
Clear(); // IServerSystem chain
BaseClass::Shutdown(); }
//-----------------------------------------------------------------------------
// Purpose: Instance a custom response system
// Input : *scriptfile -
// Output : IResponseSystem
//-----------------------------------------------------------------------------
IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile ) { return defaultresponsesytem.PrecacheCustomResponseSystem( scriptfile ); }
//-----------------------------------------------------------------------------
// Purpose: Instance a custom response system
// Input : *scriptfile -
// set -
// Output : IResponseSystem
//-----------------------------------------------------------------------------
IResponseSystem *BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore ) { return defaultresponsesytem.BuildCustomResponseSystemGivenCriteria( pszBaseFile, pszCustomName, criteriaSet, flCriteriaScore ); }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void DestroyCustomResponseSystems() { defaultresponsesytem.DestroyCustomResponseSystems(); }
|