source-sdk-2013-mapbase/sp/src/game/shared/ai_responsesystem_new.cpp
2021-04-25 23:06:19 +02:00

1370 lines
35 KiB
C++

//========= 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"
#ifdef GAME_DLL
#include "ai_speech.h"
#endif
#include "tier0/icommandline.h"
#include <ctype.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/runtime/response_types_internal.h"
#include "scenefilecache/ISceneFileCache.h"
#ifdef GAME_DLL
#include "sceneentity.h"
#endif
#include "networkstringtabledefs.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#undef IResponseSystem
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 void CC_RR_DumpHashInfo( const CCommand &args );
#ifdef MAPBASE
ConVar rr_enhanced_saverestore( "rr_enhanced_saverestore", "0", FCVAR_NONE, "Enables enhanced save/restore capabilities for the Response System." );
#endif
extern ISceneFileCache *scenefilecache;
extern INetworkStringTable *g_pStringTableClientSideChoreoScenes;
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;
}
#ifdef MAPBASE
// A version of the above which preserves casing and supports escaped quotes
inline const char *RR_Parse_Preserve(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 == '\"')
{
bool escaped = false;
data++;
while (1)
{
c = *data++;
if ((c=='\"' && !escaped) || !c)
{
token[len] = 0;
return data;
}
escaped = (c == '\\');
if (!escaped)
{
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] = c;
data++;
len++;
c = *data;
if ( IN_CHARACTERSET( *breaks, c ) )
break;
} while (c>32);
token[len] = 0;
return data;
}
#endif
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 );
}
#ifdef MAPBASE
/// (Optional) Same as ParseFile, but with casing preserved and escaped quotes supported
virtual const char *ParseFilePreserve( const char *data, char *token, int maxlen )
{
NOTE_UNUSED( maxlen );
return RR_Parse_Preserve( data, token );
}
#endif
/// 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:
//-----------------------------------------------------------------------------
#if 0
class CScenePrecacheSystem : public CAutoGameSystem
{
public:
CScenePrecacheSystem() : CAutoGameSystem( "CScenePrecacheSystem" ), m_RepeatCounts( 0, 0, DefLessFunc( int ) )
{
}
// Level init, shutdown
virtual void LevelShutdownPreEntity()
{
m_RepeatCounts.Purge();
}
bool ShouldPrecache( char const *pszScene )
{
int hash = HashStringCaselessConventional( pszScene );
int slot = m_RepeatCounts.Find( hash );
if ( slot != m_RepeatCounts.InvalidIndex() )
{
m_RepeatCounts[ slot ]++;
return false;
}
m_RepeatCounts.Insert( hash, 0 );
return true;
}
private:
CUtlMap< int, int > m_RepeatCounts;
};
static CScenePrecacheSystem g_ScenePrecacheSystem;
//-----------------------------------------------------------------------------
// Purpose: Used for precaching instanced scenes
// Input : *pszScene -
//-----------------------------------------------------------------------------
void PrecacheInstancedScene( char const *pszScene )
{
static int nMakingReslists = -1;
if ( !g_ScenePrecacheSystem.ShouldPrecache( pszScene ) )
return;
if ( nMakingReslists == -1 )
{
nMakingReslists = CommandLine()->FindParm( "-makereslists" ) > 0 ? 1 : 0;
}
if ( nMakingReslists == 1 )
{
// Just stat the file to add to reslist
g_pFullFileSystem->Size( pszScene );
}
// verify existence, cache is pre-populated, should be there
SceneCachedData_t sceneData;
if ( !scenefilecache->GetSceneCachedData( pszScene, &sceneData ) )
{
// Scenes are sloppy and don't always exist.
// A scene that is not in the pre-built cache image, but on disk, is a true error.
if ( IsX360() && ( g_pFullFileSystem->GetDVDMode() != DVDMODE_STRICT ) && g_pFullFileSystem->FileExists( pszScene, "GAME" ) )
{
Warning( "PrecacheInstancedScene: Missing scene '%s' from scene image cache.\nRebuild scene image cache!\n", pszScene );
}
}
else
{
for ( int i = 0; i < sceneData.numSounds; ++i )
{
short stringId = scenefilecache->GetSceneCachedSound( sceneData.sceneId, i );
CBaseEntity::PrecacheScriptSound( scenefilecache->GetSceneString( stringId ) );
}
}
g_pStringTableClientSideChoreoScenes->AddString( CBaseEntity::IsServer(), pszScene );
}
#endif
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();
}
#ifdef MAPBASE
if (!rr_enhanced_saverestore.GetBool() || gpGlobals->eLoadType != MapLoad_Transition)
#endif
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( rr_reloadresponsesystems, "Reload all response system scripts." )
{
#ifdef GAME_DLL
if ( !UTIL_IsCommandIssuedByServerAdmin() )
return;
#endif
defaultresponsesytem.ReloadAllResponseSystems();
}
#if RR_DUMPHASHINFO_ENABLED
static void CC_RR_DumpHashInfo( const CCommand &args )
{
defaultresponsesytem.m_RulePartitions.PrintBucketInfo( &defaultresponsesytem );
}
static ConCommand rr_dumphashinfo( "rr_dumphashinfo", CC_RR_DumpHashInfo, "Statistics on primary hash bucketing of response rule partitions");
#endif
#ifdef MAPBASE
// Designed for extern magic, this gives the <, >, etc. of response system criteria to the outside world.
// Mostly just used for Matcher_Match in matchers.h.
bool ResponseSystemCompare( const char *criterion, const char *value )
{
Criteria criteria;
criteria.value = criterion;
defaultresponsesytem.ComputeMatcher( &criteria, criteria.matcher );
return defaultresponsesytem.CompareUsingMatcher( value, criteria.matcher, true );
return false;
}
//-----------------------------------------------------------------------------
// CResponseFilePrecacher
//
// Purpose: Precaches a single talker file. That's it.
//
// It copies from a bunch of the original Response System class and therefore it's really messy.
// Despite the horrors a normal programmer might find in here, I think it performs better than anything else I could've come up with.
//-----------------------------------------------------------------------------
/*
class CResponseFilePrecacher
{
public:
// Stuff copied from the Response System.
// Direct copy-pastes are very compact, to say the least.
inline bool ParseToken( void )
{
if ( m_bUnget )
{ m_bUnget = false; return true; }
if ( m_ScriptStack.Count() <= 0 )
{ return false; }
m_ScriptStack[ 0 ].currenttoken = engine->ParseFile( m_ScriptStack[ 0 ].currenttoken, token, sizeof( token ) );
m_ScriptStack[ 0 ].tokencount++;
return m_ScriptStack[ 0 ].currenttoken != NULL ? true : false;
}
CUtlVector< CResponseSystem::ScriptEntry > m_ScriptStack;
bool m_bUnget;
char token[ 1204 ];
void PrecacheResponse( const char *response, byte type )
{
switch ( type )
{
default:
break;
case RESPONSE_SCENE:
{
DevMsg("Precaching scene %s...\n", response);
// fixup $gender references
char file[_MAX_PATH];
Q_strncpy( file, response, 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];
Q_snprintf( genderFile, sizeof(genderFile), "%smale%s", file, postGender);
PrecacheInstancedScene( genderFile );
Q_snprintf( genderFile, sizeof(genderFile), "%sfemale%s", file, postGender);
PrecacheInstancedScene( genderFile );
}
else
{
PrecacheInstancedScene( file );
}
}
break;
case RESPONSE_SPEAK:
{
DevMsg("Precaching sound %s...\n", response);
CBaseEntity::PrecacheScriptSound( response );
}
break;
}
}
bool IsRootCommand()
{
if (!Q_stricmp( token, "#include" ) || !Q_stricmp( token, "response" )
|| !Q_stricmp( token, "enumeration" ) || !Q_stricmp( token, "criteria" )
|| !Q_stricmp( token, "criterion" ) || !Q_stricmp( token, "rule" ))
return true;
return false;
}
void ParseResponse( void )
{
// Must go to response group name
ParseToken();
while ( 1 )
{
ParseToken();
if ( !Q_stricmp( token, "{" ) )
{
while ( 1 )
{
ParseToken();
if ( !Q_stricmp( token, "}" ) )
break;
byte type = ComputeResponseType( token );
if (type == RESPONSE_NONE)
continue;
ParseToken();
char *value = CopyString( token );
PrecacheResponse(value, type);
}
break;
}
byte type = ComputeResponseType( token );
if (type == RESPONSE_NONE)
break;
ParseToken();
char *value = CopyString( token );
PrecacheResponse(value, type);
break;
}
}
bool LoadFromBuffer(const char *scriptfile, unsigned char *buffer, CStringPool &includedFiles)
{
includedFiles.Allocate( scriptfile );
CResponseSystem::ScriptEntry e;
e.name = filesystem->FindOrAddFileName( scriptfile );
e.buffer = buffer;
e.currenttoken = (char *)e.buffer;
e.tokencount = 0;
m_ScriptStack.AddToHead( e );
while ( 1 )
{
ParseToken();
if ( !token[0] )
{
break;
}
if ( !Q_stricmp( token, "response" ) )
{
ParseResponse();
}
else if ( !Q_stricmp( token, "#include" ) || !Q_stricmp( token, "#base" ) )
{
// Compacted version of ParseInclude(), including new changes.
// Look at that if you want to read.
char includefile[ 256 ];
ParseToken();
if (scriptfile) { size_t len = strlen(scriptfile)-1;
for (size_t i = 0; i < len; i++)
{ if (scriptfile[i] == CORRECT_PATH_SEPARATOR || scriptfile[i] == INCORRECT_PATH_SEPARATOR)
{ len = i; }
} Q_strncpy(includefile, scriptfile, len+1);
if (len+1 != strlen(scriptfile))
{ Q_snprintf(includefile, sizeof(includefile), "%s/%s", includefile, token); }
else includefile[0] = '\0';
} if (!includefile[0]) Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token );
if ( includedFiles.Find( includefile ) == NULL )
{
MEM_ALLOC_CREDIT();
// Try and load it
CUtlBuffer buf;
if ( filesystem->ReadFile( includefile, "GAME", buf ) )
{
LoadFromBuffer( includefile, (unsigned char *)buf.PeekGet(), includedFiles );
}
}
}
}
if ( m_ScriptStack.Count() > 0 )
m_ScriptStack.Remove( 0 );
return true;
}
};
*/
// Loads a file directly to the main response system
bool LoadResponseSystemFile(const char *scriptfile)
{
CUtlBuffer buf;
if ( !filesystem->ReadFile( scriptfile, "GAME", buf ) )
{
return false;
}
// This is a really messy and specialized system that precaches the responses and only the responses of a talker file.
/*
CStringPool includedFiles;
CResponseFilePrecacher *rs = new CResponseFilePrecacher();
if (!rs || !rs->LoadFromBuffer(scriptfile, (unsigned char *)buf.PeekGet(), includedFiles))
{
Warning( "Failed to load response system data from %s", scriptfile );
delete rs;
return false;
}
delete rs;
*/
// HACKHACK: This is not very efficient
/*
CInstancedResponseSystem *tempSys = new CInstancedResponseSystem( scriptfile );
if ( tempSys && tempSys->Init() )
{
tempSys->Precache();
for ( ResponseRulePartition::tIndex idx = tempSys->m_RulePartitions.First() ;
tempSys->m_RulePartitions.IsValid(idx) ;
idx = tempSys->m_RulePartitions.Next(idx) )
{
Rule &rule = tempSys->m_RulePartitions[idx];
tempSys->CopyRuleFrom( &rule, idx, &defaultresponsesytem );
}
tempSys->Release();
}
*/
// HACKHACK: This is even less efficient
defaultresponsesytem.LoadFromBuffer( scriptfile, (const char *)buf.PeekGet() );
defaultresponsesytem.Precache();
return true;
}
// Called from Mapbase manifests to flush
void ReloadResponseSystem()
{
defaultresponsesytem.ReloadAllResponseSystems();
}
#endif
static short RESPONSESYSTEM_SAVE_RESTORE_VERSION = 1;
// note: this won't save/restore settings from instanced response systems. Could add that with a CDefSaveRestoreOps implementation if needed
//
class CDefaultResponseSystemSaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler
{
public:
const char *GetBlockName()
{
return "ResponseSystem";
}
void WriteSaveHeaders( ISave *pSave )
{
pSave->WriteShort( &RESPONSESYSTEM_SAVE_RESTORE_VERSION );
}
void ReadRestoreHeaders( IRestore *pRestore )
{
// No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so.
short version;
pRestore->ReadShort( &version );
m_fDoLoad = ( version == RESPONSESYSTEM_SAVE_RESTORE_VERSION );
}
void Save( ISave *pSave )
{
CDefaultResponseSystem& rs = defaultresponsesytem;
int count = rs.m_Responses.Count();
pSave->WriteInt( &count );
for ( int i = 0; i < count; ++i )
{
pSave->StartBlock( "ResponseGroup" );
pSave->WriteString( rs.m_Responses.GetElementName( i ) );
const ResponseGroup *group = &rs.m_Responses[ i ];
pSave->WriteAll( group );
short groupCount = group->group.Count();
pSave->WriteShort( &groupCount );
for ( int j = 0; j < groupCount; ++j )
{
const ParserResponse *response = &group->group[ j ];
pSave->StartBlock( "Response" );
pSave->WriteString( response->value );
pSave->WriteAll( response );
pSave->EndBlock();
}
pSave->EndBlock();
}
#ifdef MAPBASE
// Enhanced Response System save/restore
int count2 = 0;
if (rr_enhanced_saverestore.GetBool())
{
// Rule state save/load
count2 = rs.m_RulePartitions.Count();
pSave->WriteInt( &count2 );
for ( ResponseRulePartition::tIndex idx = rs.m_RulePartitions.First() ;
rs.m_RulePartitions.IsValid(idx) ;
idx = rs.m_RulePartitions.Next(idx) )
{
pSave->StartBlock( "Rule" );
pSave->WriteString( rs.m_RulePartitions.GetElementName( idx ) );
const Rule &rule = rs.m_RulePartitions[ idx ];
bool bEnabled = rule.m_bEnabled;
pSave->WriteBool( &bEnabled );
pSave->EndBlock();
}
}
else
{
// Indicate this isn't using enhanced save/restore
pSave->WriteInt( &count2 );
}
#endif
}
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();
}
#ifdef MAPBASE
// Enhanced Response System save/restore
count = pRestore->ReadInt();
for ( int i = 0; i < count; ++i )
{
char szRuleBlockName[SIZE_BLOCK_NAME_BUF];
pRestore->StartBlock( szRuleBlockName );
if ( !Q_stricmp( szRuleBlockName, "Rule" ) )
{
char groupname[ 256 ];
pRestore->ReadString( groupname, sizeof( groupname ), 0 );
// Try and find it
Rule *rule = rs.m_RulePartitions.FindByName( groupname );
if ( rule )
{
bool bEnabled;
pRestore->ReadBool( &bEnabled );
rule->m_bEnabled = bEnabled;
}
else
{
Warning("Warning: Can't find rule %s\n", groupname);
}
}
pRestore->EndBlock();
}
#endif
}
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();
}