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

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

ConVar rr_debugresponses( "rr_debugresponses", "0", FCVAR_NONE, "Show verbose matching output (1 for simple, 2 for rule scoring). If set to 3, it will only show response success/failure for npc_selected NPCs." );
ConVar rr_debugrule( "rr_debugrule", "", FCVAR_NONE, "If set to the name of the rule, that rule's score will be shown whenever a concept is passed into the response rules system.");
ConVar rr_dumpresponses( "rr_dumpresponses", "0", FCVAR_NONE, "Dump all response_rules.txt and rules (requires restart)" );

static CUtlSymbolTable g_RS;

inline static char *CopyString( const char *in )
{
	if ( !in )
		return NULL;

	int len = Q_strlen( in );
	char *out = new char[ len + 1 ];
	Q_memcpy( out, in, len );
	out[ len ] = 0;
	return out;
}

#pragma pack(1)
class Matcher
{
public:
	Matcher()
	{
		valid = false;
		isnumeric = false;
		notequal = false;
		usemin = false;
		minequals = false;
		usemax = false;
		maxequals = false;
		maxval = 0.0f;
		minval = 0.0f;

		token = UTL_INVAL_SYMBOL;
		rawtoken = UTL_INVAL_SYMBOL;
	}

	void Describe( void )
	{
		if ( !valid )
		{
			DevMsg( "    invalid!\n" );
			return;
		}
		char sz[ 128 ];

		sz[ 0] = 0;
		int minmaxcount = 0;
		if ( usemin )
		{
			Q_snprintf( sz, sizeof( sz ), ">%s%.3f", minequals ? "=" : "", minval );
			minmaxcount++;
		}
		if ( usemax )
		{
			char sz2[ 128 ];
			Q_snprintf( sz2, sizeof( sz2 ), "<%s%.3f", maxequals ? "=" : "", maxval );

			if ( minmaxcount > 0 )
			{
				Q_strncat( sz, " and ", sizeof( sz ), COPY_ALL_CHARACTERS );
			}
			Q_strncat( sz, sz2, sizeof( sz ), COPY_ALL_CHARACTERS );
			minmaxcount++;
		}

		if ( minmaxcount >= 1 )
		{
			DevMsg( "    matcher:  %s\n", sz );
			return;
		}

		if ( notequal )
		{
			DevMsg( "    matcher:  !=%s\n", GetToken() );
			return;
		}

		DevMsg( "    matcher:  ==%s\n", GetToken() );
	}

	float	maxval;
	float	minval;

	bool	valid : 1;      //1
	bool	isnumeric : 1;  //2
	bool	notequal : 1;   //3
	bool	usemin : 1;     //4
	bool	minequals : 1;  //5
	bool	usemax : 1;     //6
	bool	maxequals : 1;  //7

	void	SetToken( char const *s )
	{
		token = g_RS.AddString( s );
	}

	char const *GetToken()
	{
		if ( token.IsValid() )
		{
			return g_RS.String( token );
		}
		return "";
	}
	void	SetRaw( char const *raw )
	{
		rawtoken = g_RS.AddString( raw );
	}
	char const *GetRaw()
	{
		if ( rawtoken.IsValid() )
		{
			return g_RS.String( rawtoken );
		}
		return "";
	}

private:
	CUtlSymbol	token;
	CUtlSymbol	rawtoken;
};

struct Response
{
	DECLARE_SIMPLE_DATADESC();

	Response()
	{
		type = RESPONSE_NONE;
		value = NULL;
		weight.SetFloat( 1.0f );
		depletioncount = 0;
		first = false;
		last = false;
	}

	Response( const Response& src )
	{
		weight = src.weight;
		type = src.type;
		value = CopyString( src.value );
		depletioncount = src.depletioncount;
		first = src.first;
		last = src.last;
	}

	Response& operator =( const Response& src )
	{
		if ( this == &src )
			return *this;
		weight = src.weight;
		type = src.type;
		value = CopyString( src.value );
		depletioncount = src.depletioncount;
		first = src.first;
		last = src.last;
		return *this;
	}

	~Response()
	{
		delete[] value;
	}

	ResponseType_t GetType() { return (ResponseType_t)type; }

	char						*value;  // fixed up value spot		// 4
	float16						weight;								// 6

	byte						depletioncount;						// 7
	byte						type : 6;							// 8
	byte						first : 1;							// 
	byte						last : 1;							// 
};

struct ResponseGroup
{
	DECLARE_SIMPLE_DATADESC();

	ResponseGroup()
	{
		// By default visit all nodes before repeating
		m_bSequential = false;
		m_bNoRepeat = false;
		m_bEnabled = true;
		m_nCurrentIndex = 0;
		m_bDepleteBeforeRepeat = true;
		m_nDepletionCount = 1;
		m_bHasFirst = false;
		m_bHasLast = false;
	}

	ResponseGroup( const ResponseGroup& src )
	{
		int c = src.group.Count();
		for ( int i = 0; i < c; i++ )
		{
			group.AddToTail( src.group[ i ] );
		}

		rp = src.rp;
		m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat;
		m_nDepletionCount = src.m_nDepletionCount;
		m_bHasFirst = src.m_bHasFirst;
		m_bHasLast = src.m_bHasLast;
		m_bSequential = src.m_bSequential;
		m_bNoRepeat = src.m_bNoRepeat;
		m_bEnabled = src.m_bEnabled;
		m_nCurrentIndex = src.m_nCurrentIndex;
	}

	ResponseGroup& operator=( const ResponseGroup& src )
	{
		if ( this == &src )
			return *this;
		int c = src.group.Count();
		for ( int i = 0; i < c; i++ )
		{
			group.AddToTail( src.group[ i ] );
		}

		rp = src.rp;
		m_bDepleteBeforeRepeat = src.m_bDepleteBeforeRepeat;
		m_nDepletionCount = src.m_nDepletionCount;
		m_bHasFirst = src.m_bHasFirst;
		m_bHasLast = src.m_bHasLast;
		m_bSequential = src.m_bSequential;
		m_bNoRepeat = src.m_bNoRepeat;
		m_bEnabled = src.m_bEnabled;
		m_nCurrentIndex = src.m_nCurrentIndex;
		return *this;
	}

	bool	HasUndepletedChoices() const
	{
		if ( !m_bDepleteBeforeRepeat )
			return true;

		int c = group.Count();
		for ( int i = 0; i < c; i++ )
		{
			if ( group[ i ].depletioncount != m_nDepletionCount )
				return true;
		}

		return false;
	}

	void	MarkResponseUsed( int idx )
	{
		if ( !m_bDepleteBeforeRepeat )
			return;

		if ( idx < 0 || idx >= group.Count() )
		{
			Assert( 0 );
			return;
		}

		group[ idx ].depletioncount = m_nDepletionCount;
	}

	void	ResetDepletionCount()
	{
		if ( !m_bDepleteBeforeRepeat )
			return;
		++m_nDepletionCount;
	}

	void	Reset()
	{
		ResetDepletionCount();
		SetEnabled( true );
		SetCurrentIndex( 0 );
		m_nDepletionCount = 1;

		for ( int i = 0; i < group.Count(); ++i )
		{
			group[ i ].depletioncount = 0;
		}
	}

	bool HasUndepletedFirst( int& index )
	{
		index = -1;

		if ( !m_bDepleteBeforeRepeat )
			return false;

		int c = group.Count();
		for ( int i = 0; i < c; i++ )
		{
			Response *r = &group[ i ];

			if ( ( r->depletioncount != m_nDepletionCount ) && r->first )
			{
				index = i;
				return true;
			}
		}

		return false;
	}
	
	bool HasUndepletedLast( int& index )
	{
		index = -1;

		if ( !m_bDepleteBeforeRepeat )
			return false;

		int c = group.Count();
		for ( int i = 0; i < c; i++ )
		{
			Response *r = &group[ i ];

			if ( ( r->depletioncount != m_nDepletionCount ) && r->last )
			{
				index = i;
				return true;
			}
		}

		return false;
	}

	bool	ShouldCheckRepeats() const { return m_bDepleteBeforeRepeat; }
	int		GetDepletionCount() const { return m_nDepletionCount; }

	bool	IsSequential() const { return m_bSequential; }
	void	SetSequential( bool seq ) { m_bSequential = seq; }

	bool	IsNoRepeat() const { return m_bNoRepeat; }
	void	SetNoRepeat( bool norepeat ) { m_bNoRepeat = norepeat; }

	bool	IsEnabled() const { return m_bEnabled; }
	void	SetEnabled( bool enabled ) { m_bEnabled = enabled; }

	int		GetCurrentIndex() const { return m_nCurrentIndex; }
	void	SetCurrentIndex( byte idx ) { m_nCurrentIndex = idx; }

	CUtlVector< Response >	group;

	AI_ResponseParams		rp;

	bool					m_bEnabled;

	byte					m_nCurrentIndex;
	// Invalidation counter
	byte					m_nDepletionCount;

	// Use all slots before repeating any
	bool					m_bDepleteBeforeRepeat : 1;
	bool					m_bHasFirst : 1;
	bool					m_bHasLast : 1;
	bool					m_bSequential : 1;
	bool					m_bNoRepeat : 1;
	
};

struct Criteria
{
	Criteria()
	{
		name = NULL;
		value = NULL;
		weight.SetFloat( 1.0f );
		required = false;
	}
	Criteria& operator =(const Criteria& src )
	{
		if ( this == &src )
			return *this;

		name = CopyString( src.name );
		value = CopyString( src.value );
		weight = src.weight;
		required = src.required;

		matcher = src.matcher;

		int c = src.subcriteria.Count();
		for ( int i = 0; i < c; i++ )
		{
			subcriteria.AddToTail( src.subcriteria[ i ] );
		}

		return *this;
	}
	Criteria(const Criteria& src )
	{
		name = CopyString( src.name );
		value = CopyString( src.value );
		weight = src.weight;
		required = src.required;

		matcher = src.matcher;

		int c = src.subcriteria.Count();
		for ( int i = 0; i < c; i++ )
		{
			subcriteria.AddToTail( src.subcriteria[ i ] );
		}
	}
	~Criteria()
	{
		delete[] name;
		delete[] value;
	}

	bool IsSubCriteriaType() const
	{
		return ( subcriteria.Count() > 0 ) ? true : false;
	}

	char						*name;
	char						*value;
	float16						weight;
	bool						required;

	Matcher						matcher;

	// Indices into sub criteria
	CUtlVector< unsigned short >	subcriteria;
};

struct Rule
{
	Rule()
	{
		m_bMatchOnce = false;
		m_bEnabled = true;
		m_szContext = NULL;
		m_bApplyContextToWorld = false;
	}

	Rule& operator =( const Rule& src )
	{
		if ( this == &src )
			return *this;

		int i;
		int c;
		
		c = src.m_Criteria.Count(); 
		for ( i = 0; i < c; i++ )
		{
			m_Criteria.AddToTail( src.m_Criteria[ i ] );
		}

		c = src.m_Responses.Count(); 
		for ( i = 0; i < c; i++ )
		{
			m_Responses.AddToTail( src.m_Responses[ i ] );
		}

		SetContext( src.m_szContext );
		m_bMatchOnce = src.m_bMatchOnce;
		m_bEnabled = src.m_bEnabled;
		m_bApplyContextToWorld = src.m_bApplyContextToWorld;
		return *this;
	}

	Rule( const Rule& src )
	{
		int i;
		int c;
		
		c = src.m_Criteria.Count(); 
		for ( i = 0; i < c; i++ )
		{
			m_Criteria.AddToTail( src.m_Criteria[ i ] );
		}

		c = src.m_Responses.Count(); 
		for ( i = 0; i < c; i++ )
		{
			m_Responses.AddToTail( src.m_Responses[ i ] );
		}

		SetContext( src.m_szContext );
		m_bMatchOnce = src.m_bMatchOnce;
		m_bEnabled = src.m_bEnabled;
		m_bApplyContextToWorld = src.m_bApplyContextToWorld;
	}

	~Rule()
	{
		delete[] m_szContext;
	}

	void SetContext( const char *context )
	{
		delete[] m_szContext;
		m_szContext = CopyString( context );
	}

	const char *GetContext( void ) const { return m_szContext; }

	bool	IsEnabled() const { return m_bEnabled; }
	void	Disable() { m_bEnabled = false; }
	bool	IsMatchOnce() const { return m_bMatchOnce; }
	bool	IsApplyContextToWorld() const { return m_bApplyContextToWorld; }

	// Indices into underlying criteria and response dictionaries
	CUtlVector< unsigned short >	m_Criteria;
	CUtlVector< unsigned short>		m_Responses;

	char				*m_szContext;
	bool				m_bApplyContextToWorld : 1;

	bool				m_bMatchOnce : 1;
	bool				m_bEnabled : 1;
};
#pragma pack()

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
abstract_class CResponseSystem : public IResponseSystem 
{
public:
	CResponseSystem();
	~CResponseSystem();

	// IResponseSystem
	virtual bool FindBestResponse( const AI_CriteriaSet& set, AI_Response& response, IResponseFilter *pFilter = NULL );
	virtual void GetAllResponses( CUtlVector<AI_Response *> *pResponses );

	virtual void Release() = 0;

	virtual void DumpRules();

	virtual void Precache();

	virtual void PrecacheResponses( bool bEnable )
	{
		m_bPrecache = bEnable;
	}

	bool		ShouldPrecache()	{ return m_bPrecache; }
	bool		IsCustomManagable()	{ return m_bCustomManagable; }

	void		Clear();

	void		DumpDictionary( const char *pszName );

protected:

	virtual const char *GetScriptFile( void ) = 0;
	void		LoadRuleSet( const char *setname );

	void		ResetResponseGroups();

	float		LookForCriteria( const AI_CriteriaSet &criteriaSet, int iCriteria );
	float		RecursiveLookForCriteria( const AI_CriteriaSet &criteriaSet, Criteria *pParent );

public:

	void		CopyRuleFrom( Rule *pSrcRule, int iRule, CResponseSystem *pCustomSystem );
	void		CopyCriteriaFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem );
	void		CopyResponsesFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem );
	void		CopyEnumerationsFrom( CResponseSystem *pCustomSystem );

//private:

	struct Enumeration
	{
		float		value;
	};

	struct ResponseSearchResult
	{
		ResponseSearchResult()
		{
			group = NULL;
			action = NULL;
		}

		ResponseGroup	*group;
		Response		*action;
	};

	inline bool ParseToken( void )
	{
		if ( m_bUnget )
		{
			m_bUnget = false;
			return true;
		}
		if ( m_ScriptStack.Count() <= 0 )
		{
			Assert( 0 );
			return false;
		}

		m_ScriptStack[ 0 ].currenttoken = engine->ParseFile( m_ScriptStack[ 0 ].currenttoken, token, sizeof( token ) );
		m_ScriptStack[ 0 ].tokencount++;
		return m_ScriptStack[ 0 ].currenttoken != NULL ? true : false;
	}

	inline void Unget()
	{
		m_bUnget = true;
	}

	inline bool TokenWaiting( void )
	{
		if ( m_ScriptStack.Count() <= 0 )
		{
			Assert( 0 );
			return false;
		}

		const char *p = m_ScriptStack[ 0 ].currenttoken;

		if ( !p )
		{
			Error( "AI_ResponseSystem:  Unxpected TokenWaiting() with NULL buffer in %s", m_ScriptStack[ 0 ].name );
			return false;
		}


		while ( *p && *p!='\n')
		{
			// Special handler for // comment blocks
			if ( *p == '/' && *(p+1) == '/' )
				return false;

			if ( !isspace( *p ) || isalnum( *p ) )
				return true;

			p++;
		}

		return false;
	}
	
	void		ParseOneResponse( const char *responseGroupName, ResponseGroup& group );

	void		ParseInclude( CStringPool &includedFiles );
	void		ParseResponse( void );
	void		ParseCriterion( void );
	void		ParseRule( void );
	void		ParseEnumeration( void );

	int			ParseOneCriterion( const char *criterionName );
	
	bool		Compare( const char *setValue, Criteria *c, bool verbose = false );
	bool		CompareUsingMatcher( const char *setValue, Matcher& m, bool verbose = false );
	void		ComputeMatcher( Criteria *c, Matcher& matcher );
	void		ResolveToken( Matcher& matcher, char *token, size_t bufsize, char const *rawtoken );
	float		LookupEnumeration( const char *name, bool& found );

	int			FindBestMatchingRule( const AI_CriteriaSet& set, bool verbose );

	float		ScoreCriteriaAgainstRule( const AI_CriteriaSet& set, int irule, bool verbose = false );
	float		RecursiveScoreSubcriteriaAgainstRule( const AI_CriteriaSet& set, Criteria *parent, bool& exclude, bool verbose /*=false*/ );
	float		ScoreCriteriaAgainstRuleCriteria( const AI_CriteriaSet& set, int icriterion, bool& exclude, bool verbose = false );
	bool		GetBestResponse( ResponseSearchResult& result, Rule *rule, bool verbose = false, IResponseFilter *pFilter = NULL );
	bool		ResolveResponse( ResponseSearchResult& result, int depth, const char *name, bool verbose = false, IResponseFilter *pFilter = NULL );
	int			SelectWeightedResponseFromResponseGroup( ResponseGroup *g, IResponseFilter *pFilter );
	void		DescribeResponseGroup( ResponseGroup *group, int selected, int depth );
	void		DebugPrint( int depth, const char *fmt, ... );

	void		LoadFromBuffer( const char *scriptfile, const char *buffer, CStringPool &includedFiles );

	void		GetCurrentScript( char *buf, size_t buflen );
	int			GetCurrentToken() const;
	void		SetCurrentScript( const char *script );
	bool		IsRootCommand();

	void		PushScript( const char *scriptfile, unsigned char *buffer );
	void		PopScript(void);

	void		ResponseWarning( const char *fmt, ... );

	CUtlDict< ResponseGroup, short >	m_Responses;
	CUtlDict< Criteria, short >	m_Criteria;
	CUtlDict< Rule, short >	m_Rules;
	CUtlDict< Enumeration, short > m_Enumerations;

	char		token[ 1204 ];

	bool		m_bUnget;
	bool		m_bPrecache;	

	bool		m_bCustomManagable;

	struct ScriptEntry
	{
		unsigned char	*buffer;
		FileNameHandle_t name;
		const char		*currenttoken;
		int				tokencount;
	};

	CUtlVector< ScriptEntry >		m_ScriptStack;

	friend class CDefaultResponseSystemSaveRestoreBlockHandler;
	friend class CResponseSystemSaveRestoreOps;
};

BEGIN_SIMPLE_DATADESC( Response )
	// DEFINE_FIELD( type, FIELD_INTEGER ),
	// DEFINE_ARRAY( value, FIELD_CHARACTER ),
	// DEFINE_FIELD( weight, FIELD_FLOAT ),
	DEFINE_FIELD( depletioncount, FIELD_CHARACTER ),
	// DEFINE_FIELD( first, FIELD_BOOLEAN ),
	// DEFINE_FIELD( last, FIELD_BOOLEAN ),
END_DATADESC()

BEGIN_SIMPLE_DATADESC( ResponseGroup )
	// DEFINE_FIELD( group, FIELD_UTLVECTOR ),
	// DEFINE_FIELD( rp, FIELD_EMBEDDED ),
	// DEFINE_FIELD( m_bDepleteBeforeRepeat, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_nDepletionCount, FIELD_CHARACTER ),
	// DEFINE_FIELD( m_bHasFirst, FIELD_BOOLEAN ),
	// DEFINE_FIELD( m_bHasLast, FIELD_BOOLEAN ),
	// DEFINE_FIELD( m_bSequential, FIELD_BOOLEAN ),
	// DEFINE_FIELD( m_bNoRepeat, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_nCurrentIndex, FIELD_CHARACTER ),
END_DATADESC()

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CResponseSystem::CResponseSystem()
{
	token[0] = 0;
	m_bUnget = false;
	m_bPrecache = true;
	m_bCustomManagable = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CResponseSystem::~CResponseSystem()
{
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : char const
//-----------------------------------------------------------------------------
void CResponseSystem::GetCurrentScript( char *buf, size_t buflen )
{
	Assert( buf );
	buf[ 0 ] = 0;
	if ( m_ScriptStack.Count() <= 0 )
		return;
	
	if ( filesystem->String( m_ScriptStack[ 0 ].name, buf, buflen ) )
	{
		return;
	}
	buf[ 0 ] = 0;
}

void CResponseSystem::PushScript( const char *scriptfile, unsigned char *buffer )
{
	ScriptEntry e;
	e.name = filesystem->FindOrAddFileName( scriptfile );
	e.buffer = buffer;
	e.currenttoken = (char *)e.buffer;
	e.tokencount = 0;
	m_ScriptStack.AddToHead( e );
}

void CResponseSystem::PopScript(void)
{
	Assert( m_ScriptStack.Count() >= 1 );
	if ( m_ScriptStack.Count() <= 0 )
		return;

	m_ScriptStack.Remove( 0 );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CResponseSystem::Clear()
{
	m_Responses.RemoveAll();
	m_Criteria.RemoveAll();
	m_Rules.RemoveAll();
	m_Enumerations.RemoveAll();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
//			found - 
// Output : float
//-----------------------------------------------------------------------------
float CResponseSystem::LookupEnumeration( const char *name, bool& found )
{
	int idx = m_Enumerations.Find( name );
	if ( idx == m_Enumerations.InvalidIndex() )
	{
		found = false;
		return 0.0f;
	}


	found = true;
	return m_Enumerations[ idx ].value;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : matcher - 
//-----------------------------------------------------------------------------
void CResponseSystem::ResolveToken( Matcher& matcher, char *token, size_t bufsize, char const *rawtoken )
{
	if ( rawtoken[0] != '[' )
	{
		Q_strncpy( token, rawtoken, bufsize );
		return;
	}

	// Now lookup enumeration
	bool found = false;
	float f = LookupEnumeration( rawtoken, found );
	if ( !found )
	{
		Q_strncpy( token, rawtoken, bufsize );
		ResponseWarning( "No such enumeration '%s'\n", token );
		return;
	}

	Q_snprintf( token, bufsize, "%f", f );
}


static bool AppearsToBeANumber( char const *token )
{
	if ( atof( token ) != 0.0f )
		return true;

	char const *p = token;
	while ( *p )
	{
		if ( *p != '0' )
			return false;

		p++;
	}

	return true;
}

void CResponseSystem::ComputeMatcher( Criteria *c, Matcher& matcher )
{
	const char *s = c->value;
	if ( !s )
	{
		matcher.valid = false;
		return;
	}

	const char *in = s;

	char token[ 128 ];
	char rawtoken[ 128 ];

	token[ 0 ] = 0;
	rawtoken[ 0 ] = 0;

	int n = 0;

	bool gt = false;
	bool lt = false;
	bool eq = false;
	bool nt = false;

	bool done = false;
	while ( !done )
	{
		switch( *in )
		{
		case '>':
			{
				gt = true;
				Assert( !lt ); // Can't be both
			}
			break;
		case '<':
			{
				lt = true;
				Assert( !gt );  // Can't be both
			}
			break;
		case '=':
			{
				eq = true;
			}
			break;
		case ',':
		case '\0':
			{
				rawtoken[ n ] = 0;
				n = 0;

				// Convert raw token to real token in case token is an enumerated type specifier
				ResolveToken( matcher, token, sizeof( token ), rawtoken );

				// Fill in first data set
				if ( gt )
				{
					matcher.usemin = true;
					matcher.minequals = eq;
					matcher.minval = (float)atof( token );

					matcher.isnumeric = true;
				}
				else if ( lt )
				{
					matcher.usemax = true;
					matcher.maxequals = eq;
					matcher.maxval = (float)atof( token );

					matcher.isnumeric = true;
				}
				else
				{
					if ( *in == ',' )
					{
						// If there's a comma, this better have been a less than or a gt key
						Assert( 0 );
					}

					matcher.notequal = nt;

					matcher.isnumeric = AppearsToBeANumber( token );
				}

				gt = lt = eq = nt = false;

				if ( !(*in) )
				{
					done = true;
				}
			}
			break;
		case '!':
			nt = true;
			break;
		default:
			rawtoken[ n++ ] = *in;
			break;
		}

		in++;
	}

	matcher.SetToken( token );
	matcher.SetRaw( rawtoken );
	matcher.valid = true;
}

bool CResponseSystem::CompareUsingMatcher( const char *setValue, Matcher& m, bool verbose /*=false*/ )
{
	if ( !m.valid )
		return false;

	float v = (float)atof( setValue );
	if ( setValue[0] == '[' )
	{
		bool found = false;
		v = LookupEnumeration( setValue, found );
	}
	
	int minmaxcount = 0;

	if ( m.usemin )
	{
		if ( m.minequals )
		{
			if ( v < m.minval )
				return false;
		}
		else
		{
			if ( v <= m.minval )
				return false;
		}

		++minmaxcount;
	}

	if ( m.usemax )
	{
		if ( m.maxequals )
		{
			if ( v > m.maxval )
				return false;
		}
		else
		{
			if ( v >= m.maxval )
				return false;
		}

		++minmaxcount;
	}

	// Had one or both criteria and met them
	if ( minmaxcount >= 1 )
	{
		return true;
	}

	if ( m.notequal )
	{
		if ( m.isnumeric )
		{
			if ( v == (float)atof( m.GetToken() ) )
				return false;
		}
		else
		{
			if ( !Q_stricmp( setValue, m.GetToken() ) )
				return false;
		}

		return true;
	}

	if ( m.isnumeric )
	{
		// If the setValue is "", the NPC doesn't have the key at all,
		// in which case we shouldn't match "0".
		if ( !setValue || !setValue[0] )
			return false;

		return v == (float)atof( m.GetToken() );
	}

	return !Q_stricmp( setValue, m.GetToken() ) ? true : false;
}

bool CResponseSystem::Compare( const char *setValue, Criteria *c, bool verbose /*= false*/ )
{
	Assert( c );
	Assert( setValue );

	bool bret = CompareUsingMatcher( setValue, c->matcher, verbose );

	if ( verbose )
	{
		DevMsg( "'%20s' vs. '%20s' = ", setValue, c->value );

		{
			//DevMsg( "\n" );
			//m.Describe();
		}
	}
	return bret;
}

float CResponseSystem::RecursiveScoreSubcriteriaAgainstRule( const AI_CriteriaSet& set, Criteria *parent, bool& exclude, bool verbose /*=false*/ )
{
	float score = 0.0f;
	int subcount = parent->subcriteria.Count();
	for ( int i = 0; i < subcount; i++ )
	{
		int icriterion = parent->subcriteria[ i ];

		bool excludesubrule = false;
		if (verbose)
		{
			DevMsg( "\n" );
		}
		score += ScoreCriteriaAgainstRuleCriteria( set, icriterion, excludesubrule, verbose );
	}

	exclude = ( parent->required && score == 0.0f ) ? true : false;

	return score * parent->weight.GetFloat();
}

float CResponseSystem::RecursiveLookForCriteria( const AI_CriteriaSet &criteriaSet, Criteria *pParent )
{
	float flScore = 0.0f;
	int nSubCount = pParent->subcriteria.Count();
	for ( int iSub = 0; iSub < nSubCount; ++iSub )
	{
		int iCriteria = pParent->subcriteria[iSub];
		flScore += LookForCriteria( criteriaSet, iCriteria );
	}

	return flScore;
}

float CResponseSystem::LookForCriteria( const AI_CriteriaSet &criteriaSet, int iCriteria )
{
	Criteria *pCriteria = &m_Criteria[iCriteria];
	if ( pCriteria->IsSubCriteriaType() )
	{
		return RecursiveLookForCriteria( criteriaSet, pCriteria );
	}

	int iIndex = criteriaSet.FindCriterionIndex( pCriteria->name );
	if ( iIndex == -1 )
		return 0.0f;

	Assert( criteriaSet.GetValue( iIndex ) );
	if ( Q_stricmp( criteriaSet.GetValue( iIndex ), pCriteria->value ) )
		return 0.0f;

	return 1.0f;
}

float CResponseSystem::ScoreCriteriaAgainstRuleCriteria( const AI_CriteriaSet& set, int icriterion, bool& exclude, bool verbose /*=false*/ )
{
	Criteria *c = &m_Criteria[ icriterion ];

	if ( c->IsSubCriteriaType() )
	{
		return RecursiveScoreSubcriteriaAgainstRule( set, c, exclude, verbose );
	}

	if ( verbose )
	{
		DevMsg( "  criterion '%25s':'%15s' ", m_Criteria.GetElementName( icriterion ), c->name );
	}

	exclude = false;

	float score = 0.0f;

	const char *actualValue = "";

	int found = set.FindCriterionIndex( c->name );
	if ( found != -1 )
	{
		actualValue = set.GetValue( found );
		if ( !actualValue )
		{
			Assert( 0 );
			return score;
		}
	}

	Assert( actualValue );

	if ( Compare( actualValue, c, verbose ) )
	{
		float w = set.GetWeight( found );
		score = w * c->weight.GetFloat();

		if ( verbose )
		{
			DevMsg( "matched, weight %4.2f (s %4.2f x c %4.2f)",
				score, w, c->weight.GetFloat() );
		}
	}
	else
	{
		if ( c->required )
		{
			exclude = true;
			if ( verbose )
			{
				DevMsg( "failed (+exclude rule)" );
			}
		}
		else
		{
			if ( verbose )
			{
				DevMsg( "failed" );
			}
		}
	}

	return score;
}

float CResponseSystem::ScoreCriteriaAgainstRule( const AI_CriteriaSet& set, int irule, bool verbose /*=false*/ )
{
	Rule *rule = &m_Rules[ irule ];
	float score = 0.0f;

	bool bBeingWatched = false;

	// See if we're trying to debug this rule
	const char *pszText = rr_debugrule.GetString();
	if ( pszText && pszText[0] && !Q_stricmp( pszText, m_Rules.GetElementName( irule ) ) )
	{
		bBeingWatched = true;
	}

	if ( !rule->IsEnabled() )
	{
		if ( bBeingWatched )
		{
			DevMsg("Rule '%s' is disabled.\n" );
		}
		return 0.0f;
	}

	if ( bBeingWatched )
	{
		verbose = true;
	}

	if ( verbose )
	{
		DevMsg( "Scoring rule '%s' (%i)\n{\n", m_Rules.GetElementName( irule ), irule+1 );
	}

	// Iterate set criteria
	int count = rule->m_Criteria.Count();
	int i;
	for ( i = 0; i < count; i++ )
	{
		int icriterion = rule->m_Criteria[ i ];

		bool exclude = false;
		score += ScoreCriteriaAgainstRuleCriteria( set, icriterion, exclude, verbose );

		if ( verbose )
		{
			DevMsg( ", score %4.2f\n", score );
		}

		if ( exclude ) 
		{
			score = 0.0f;
			break;
		}
	}

	if ( verbose )
	{
		DevMsg( "}\n" );
	}
	
	return score;
}

void CResponseSystem::DebugPrint( int depth, const char *fmt, ... )
{
	int indentchars = 3 * depth;
	char *indent = (char *)_alloca( indentchars + 1);
	indent[ indentchars ] = 0;
	while ( --indentchars >= 0 )
	{
		indent[ indentchars ] = ' ';
	}

	// Dump text to debugging console.
	va_list argptr;
	char szText[1024];

	va_start (argptr, fmt);
	Q_vsnprintf (szText, sizeof( szText ), fmt, argptr);
	va_end (argptr);

	DevMsg( "%s%s", indent, szText );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CResponseSystem::ResetResponseGroups()
{
	int i;
	int c = m_Responses.Count();
	for ( i = 0; i < c; i++ )
	{
		m_Responses[ i ].Reset();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *g - 
// Output : int
//-----------------------------------------------------------------------------
int CResponseSystem::SelectWeightedResponseFromResponseGroup( ResponseGroup *g, IResponseFilter *pFilter )
{
	int c = g->group.Count();
	if ( !c )
	{
		Assert( !"Expecting response group with >= 1 elements" );
		return -1;
	}

	int i;

	// Fake depletion of unavailable choices
	CUtlVector<int> fakedDepletes;
	if ( pFilter && g->ShouldCheckRepeats() )
	{
		for ( i = 0; i < c; i++ )
		{
			Response *r = &g->group[ i ];
			if ( r->depletioncount != g->GetDepletionCount() && !pFilter->IsValidResponse( r->GetType(), r->value ) )
			{
				fakedDepletes.AddToTail( i );
				g->MarkResponseUsed( i );
			}
		}
	}

	if ( !g->HasUndepletedChoices() )
	{
		g->ResetDepletionCount();

		if ( pFilter && g->ShouldCheckRepeats() )
		{
			fakedDepletes.RemoveAll();
			for ( i = 0; i < c; i++ )
			{
				Response *r = &g->group[ i ];
				if ( !pFilter->IsValidResponse( r->GetType(), r->value ) )
				{
					fakedDepletes.AddToTail( i );
					g->MarkResponseUsed( i );
				}
			}
		}

		if ( !g->HasUndepletedChoices() )
			return -1;

		// Disable the group if we looped through all the way
		if ( g->IsNoRepeat() )
		{
			g->SetEnabled( false );
			return -1;
		}
	}

	bool checkrepeats = g->ShouldCheckRepeats();
	int	depletioncount = g->GetDepletionCount();

	float totalweight = 0.0f;
	int slot = -1;

	if ( checkrepeats )
	{
		int check= -1;
		// Snag the first slot right away
		if ( g->HasUndepletedFirst( check ) && check != -1 )
		{
			slot = check;
		}

		if ( slot == -1 && g->HasUndepletedLast( check ) && check != -1 )
		{
			// If this is the only undepleted one, use it now
			for ( i = 0; i < c; i++ )
			{
				Response *r = &g->group[ i ];
				if ( checkrepeats && 
					( r->depletioncount == depletioncount ) )
				{
					continue;
				}		

				if ( r->last )
				{
					Assert( i == check );
					continue;
				}

				// There's still another undepleted entry
				break;
			}

			// No more undepleted so use the r->last slot 
			if ( i >= c )
			{
				slot = check;
			}
		}
	}

	if ( slot == -1 )
	{
		for ( i = 0; i < c; i++ )
		{
			Response *r = &g->group[ i ];
			if ( checkrepeats && 
				( r->depletioncount == depletioncount ) )
			{
				continue;
			}

			// Always skip last entry here since we will deal with it above
			if ( checkrepeats && r->last )
				continue;

			int prevSlot = slot;

			if ( !totalweight )
			{
				slot = i;
			}

			// Always assume very first slot will match
			totalweight += r->weight.GetFloat();
			if ( !totalweight || random->RandomFloat(0,totalweight) < r->weight.GetFloat() )
			{
				slot = i;
			}

			if ( !checkrepeats && slot != prevSlot && pFilter && !pFilter->IsValidResponse( r->GetType(), r->value ) )
			{
				slot = prevSlot;
				totalweight -= r->weight.GetFloat();
			}
		}
	}

	if ( slot != -1 )
		g->MarkResponseUsed( slot );

	// Revert fake depletion of unavailable choices
	if ( pFilter && g->ShouldCheckRepeats() )
	{
		for ( i = 0; i < fakedDepletes.Count(); i++ )
		{
			g->group[ fakedDepletes[ i ] ].depletioncount = 0;;
		}
	}

	return slot;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : searchResult - 
//			depth - 
//			*name - 
//			verbose - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CResponseSystem::ResolveResponse( ResponseSearchResult& searchResult, int depth, const char *name, bool verbose /*= false*/, IResponseFilter *pFilter )
{
	int responseIndex = m_Responses.Find( name );
	if ( responseIndex == m_Responses.InvalidIndex() )
		return false;

	ResponseGroup *g = &m_Responses[ responseIndex ];
	// Group has been disabled
	if ( !g->IsEnabled() )
		return false;

	int c = g->group.Count();
	if ( !c )
		return false;

	int idx = 0;

	if ( g->IsSequential() )
	{
		// See if next index is valid
		int initialIndex = g->GetCurrentIndex();
		bool bFoundValid = false;

		do 
		{
			idx = g->GetCurrentIndex();
			g->SetCurrentIndex( idx + 1 );
			if ( idx >= c )
			{
				if ( g->IsNoRepeat() )
				{
					g->SetEnabled( false );
					return false;
				}
				idx = 0;
				g->SetCurrentIndex( 0 );
			}

			if ( !pFilter || pFilter->IsValidResponse( g->group[idx].GetType(), g->group[idx].value ) )
			{
				bFoundValid = true;
				break;
			}
			
		} while ( g->GetCurrentIndex() != initialIndex );

		if ( !bFoundValid )
			return false;
	}
	else
	{
		idx = SelectWeightedResponseFromResponseGroup( g, pFilter );
		if ( idx < 0 )
			return false;
	}

	if ( verbose )
	{
		DebugPrint( depth, "%s\n", m_Responses.GetElementName( responseIndex ) );
		DebugPrint( depth, "{\n" );
		DescribeResponseGroup( g, idx, depth );
	}

	bool bret = true;

	Response *result = &g->group[ idx ];
	if ( result->type == RESPONSE_RESPONSE )
	{
		// Recurse
		bret = ResolveResponse( searchResult, depth + 1, result->value, verbose, pFilter );
	}
	else
	{
		searchResult.action = result;
		searchResult.group	= g;
	}

	if( verbose )
	{
		DebugPrint( depth, "}\n" );
	}

	return bret;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *group - 
//			selected - 
//			depth - 
//-----------------------------------------------------------------------------
void CResponseSystem::DescribeResponseGroup( ResponseGroup *group, int selected, int depth )
{
	int c = group->group.Count();

	for ( int i = 0; i < c ; i++ )
	{
		Response *r = &group->group[ i ];
		DebugPrint( depth + 1, "%s%20s : %40s %5.3f\n",
			i == selected ? "-> " : "   ",
			AI_Response::DescribeResponse( r->GetType() ),
			r->value,
			r->weight.GetFloat() );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *rule - 
// Output : CResponseSystem::Response
//-----------------------------------------------------------------------------
bool CResponseSystem::GetBestResponse( ResponseSearchResult& searchResult, Rule *rule, bool verbose /*=false*/, IResponseFilter *pFilter )
{
	int c = rule->m_Responses.Count();
	if ( !c )
		return false;

	int index = random->RandomInt( 0, c - 1 );
	int groupIndex = rule->m_Responses[ index ];

	ResponseGroup *g = &m_Responses[ groupIndex ];

	// Group has been disabled
	if ( !g->IsEnabled() )
		return false;

	int count = g->group.Count();
	if ( !count )
		return false;

	int responseIndex = 0;

	if ( g->IsSequential() )
	{
		// See if next index is valid
		int initialIndex = g->GetCurrentIndex();
		bool bFoundValid = false;

		do 
		{
			responseIndex = g->GetCurrentIndex();
			g->SetCurrentIndex( responseIndex + 1 );
			if ( responseIndex >= count )
			{
				if ( g->IsNoRepeat() )
				{
					g->SetEnabled( false );
					return false;
				}
				responseIndex = 0;
				g->SetCurrentIndex( 0 );
			}

			if ( !pFilter || pFilter->IsValidResponse( g->group[responseIndex].GetType(), g->group[responseIndex].value ) )
			{
				bFoundValid = true;
				break;
			}
			
		} while ( g->GetCurrentIndex() != initialIndex );

		if ( !bFoundValid )
			return false;
	}
	else
	{
		responseIndex = SelectWeightedResponseFromResponseGroup( g, pFilter );
		if ( responseIndex < 0 )
			return false;
	}


	Response *r = &g->group[ responseIndex ];

	int depth = 0;

	if ( verbose )
	{
		DebugPrint( depth, "%s\n", m_Responses.GetElementName( groupIndex ) );
		DebugPrint( depth, "{\n" );

		DescribeResponseGroup( g, responseIndex, depth );
	}

	bool bret = true;

	if ( r->type == RESPONSE_RESPONSE )
	{
		bret = ResolveResponse( searchResult, depth + 1, r->value, verbose, pFilter );
	}
	else
	{
		searchResult.action = r;
		searchResult.group	= g;
	}

	if ( verbose )
	{
		DebugPrint( depth, "}\n" );
	}

	return bret;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : set - 
//			verbose - 
// Output : int
//-----------------------------------------------------------------------------
int CResponseSystem::FindBestMatchingRule( const AI_CriteriaSet& set, bool verbose )
{
	CUtlVector< int >	bestrules;
	float bestscore = 0.001f;

	int c = m_Rules.Count();
	int i;
	for ( i = 0; i < c; i++ )
	{
		float score = ScoreCriteriaAgainstRule( set, i, verbose );
		// Check equals so that we keep track of all matching rules
		if ( score >= bestscore )
		{
			// Reset bucket
			if( score != bestscore )
			{
				bestscore = score;
				bestrules.RemoveAll();
			}

			// Add to bucket
			bestrules.AddToTail( i );
		}
	}

	int bestCount = bestrules.Count();
	if ( bestCount <= 0 )
		return -1;

	if ( bestCount == 1 )
		return bestrules[ 0 ];

	// Randomly pick one of the tied matching rules
	int idx = random->RandomInt( 0, bestCount - 1 );
	if ( verbose )
	{
		DevMsg( "Found %i matching rules, selecting slot %i\n", bestCount, idx );
	}
	return bestrules[ idx ];
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : set - 
// Output : AI_Response
//-----------------------------------------------------------------------------
bool CResponseSystem::FindBestResponse( const AI_CriteriaSet& set, AI_Response& response, IResponseFilter *pFilter )
{
	bool valid = false;

	int iDbgResponse = rr_debugresponses.GetInt();
	bool showRules = ( iDbgResponse == 2 );
	bool showResult = ( iDbgResponse == 1 || iDbgResponse == 2 );

	// Look for match. verbose mode used to be at level 2, but disabled because the writers don't actually care for that info.
	int bestRule = FindBestMatchingRule( set, false ); 

	ResponseType_t responseType = RESPONSE_NONE;
	AI_ResponseParams rp;

	char ruleName[ 128 ];
	char responseName[ 128 ];
	const char *context;
	bool bcontexttoworld;
	ruleName[ 0 ] = 0;
	responseName[ 0 ] = 0;
	context = NULL;
	bcontexttoworld = false;
	if ( bestRule != -1 )
	{
		Rule *r = &m_Rules[ bestRule ];

		ResponseSearchResult result;
		if ( GetBestResponse( result, r, showResult, pFilter ) )
		{
			Q_strncpy( responseName, result.action->value, sizeof( responseName ) );
			responseType = result.action->GetType();
			rp = result.group->rp;
		}

		Q_strncpy( ruleName, m_Rules.GetElementName( bestRule ), sizeof( ruleName ) );

		// Disable the rule if it only allows for matching one time
		if ( r->IsMatchOnce() )
		{
			r->Disable();
		}
		context = r->GetContext();
		bcontexttoworld = r->IsApplyContextToWorld();

		valid = true;
	}

	response.Init( responseType, responseName, set, rp, ruleName, context, bcontexttoworld );

	if ( showResult )
	{
		/*
		// clipped -- chet doesn't really want this info
		if ( valid )
		{
			// Rescore the winner and dump to console
			ScoreCriteriaAgainstRule( set, bestRule, true );
		}
		*/
		
	
		if ( valid || showRules )
		{
			// Describe the response, too
			response.Describe();
		}
	}

	return valid;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CResponseSystem::GetAllResponses( CUtlVector<AI_Response *> *pResponses )
{
	for ( int i = 0; i < (int)m_Responses.Count(); i++ )
	{
		ResponseGroup &group = m_Responses[i];

		for ( int j = 0; j < group.group.Count(); j++)
		{
			Response &response = group.group[j];
			if ( response.type != RESPONSE_RESPONSE )
			{
				AI_Response *pResponse = new AI_Response;
				pResponse->Init( response.GetType(), response.value, AI_CriteriaSet(), group.rp, NULL, NULL, false );
				pResponses->AddToTail(pResponse);
			}
		}
	}
}

static void TouchFile( char const *pchFileName )
{
	filesystem->Size( pchFileName );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CResponseSystem::Precache()
{
	bool bTouchFiles = CommandLine()->FindParm( "-makereslists" ) != 0;

	// enumerate and mark all the scripts so we know they're referenced
	for ( int i = 0; i < (int)m_Responses.Count(); i++ )
	{
		ResponseGroup &group = m_Responses[i];

		for ( int j = 0; j < group.group.Count(); j++)
		{
			Response &response = group.group[j];

			switch ( response.type )
			{
			default:
				break;
			case RESPONSE_SCENE:
				{
					// fixup $gender references
					char file[_MAX_PATH];
					Q_strncpy( file, response.value, sizeof(file) );
					char *gender = strstr( file, "$gender" );
					if ( gender )
					{
						// replace with male & female
						const char *postGender = gender + strlen("$gender");
						*gender = 0;
						char genderFile[_MAX_PATH];
						// male
						Q_snprintf( genderFile, sizeof(genderFile), "%smale%s", file, postGender);

						PrecacheInstancedScene( genderFile );
						if ( bTouchFiles )
						{
							TouchFile( genderFile );
						}

						Q_snprintf( genderFile, sizeof(genderFile), "%sfemale%s", file, postGender);

						PrecacheInstancedScene( genderFile );
						if ( bTouchFiles )
						{
							TouchFile( genderFile );
						}
					}
					else
					{
						PrecacheInstancedScene( file );
						if ( bTouchFiles )
						{
							TouchFile( file );
						}
					}
				}
				break;
			case RESPONSE_SPEAK:
				{
					CBaseEntity::PrecacheScriptSound( response.value );
				}
				break;
			}
		}
	}
}

void CResponseSystem::ParseInclude( CStringPool &includedFiles )
{
	char includefile[ 256 ];
	ParseToken();
	Q_snprintf( includefile, sizeof( includefile ), "scripts/%s", token );

	// check if the file is already included
	if ( includedFiles.Find( includefile ) != NULL )
	{
		return;
	}

	MEM_ALLOC_CREDIT();

	// Try and load it
	CUtlBuffer buf;
	if ( !filesystem->ReadFile( includefile, "GAME", buf ) )
	{
		DevMsg( "Unable to load #included script %s\n", includefile );
		return;
	}

	LoadFromBuffer( includefile, (const char *)buf.PeekGet(), includedFiles );
}

void CResponseSystem::LoadFromBuffer( const char *scriptfile, const char *buffer, CStringPool &includedFiles )
{
	includedFiles.Allocate( scriptfile );
	PushScript( scriptfile, (unsigned char * )buffer );

	if( rr_dumpresponses.GetBool() )
	{
		DevMsg("Reading: %s\n", scriptfile );
	}

	while ( 1 )
	{
		ParseToken();
		if ( !token[0] )
		{
			break;
		}

		if ( !Q_stricmp( token, "#include" ) )
		{
			ParseInclude( includedFiles );
		}
		else if ( !Q_stricmp( token, "response" ) )
		{
			ParseResponse();
		}
		else if ( !Q_stricmp( token, "criterion" ) || 
			!Q_stricmp( token, "criteria" ) )
		{
			ParseCriterion();
		}
		else if ( !Q_stricmp( token, "rule" ) )
		{
			ParseRule();
		}
		else if ( !Q_stricmp( token, "enumeration" ) )
		{
			ParseEnumeration();
		}
		else
		{
			int byteoffset = m_ScriptStack[ 0 ].currenttoken - (const char *)m_ScriptStack[ 0 ].buffer;

			Error( "CResponseSystem::LoadFromBuffer:  Unknown entry type '%s', expecting 'response', 'criterion', 'enumeration' or 'rules' in file %s(offset:%i)\n", 
				token, scriptfile, byteoffset );
			break;
		}
	}

	if ( m_ScriptStack.Count() == 1 )
	{
		char cur[ 256 ];
		GetCurrentScript( cur, sizeof( cur ) );
		DevMsg( 1, "CResponseSystem:  %s (%i rules, %i criteria, and %i responses)\n",
			cur, m_Rules.Count(), m_Criteria.Count(), m_Responses.Count() );

		if( rr_dumpresponses.GetBool() )
		{
			DumpRules();
		}
	}

	PopScript();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CResponseSystem::LoadRuleSet( const char *basescript )
{
	int length = 0;
	unsigned char *buffer = (unsigned char *)UTIL_LoadFileForMe( basescript, &length );
	if ( length <= 0 || !buffer )
	{
		DevMsg( 1, "CResponseSystem:  failed to load %s\n", basescript );
		return;
	}

	CStringPool includedFiles;

	LoadFromBuffer( basescript, (const char *)buffer, includedFiles );

	UTIL_FreeFile( buffer );

	Assert( m_ScriptStack.Count() == 0 );
}

static ResponseType_t ComputeResponseType( const char *s )
{
	if ( !Q_stricmp( s, "scene" ) )
	{
		return RESPONSE_SCENE;
	}
	else if ( !Q_stricmp( s, "sentence" ) )
	{
		return RESPONSE_SENTENCE;
	}
	else if ( !Q_stricmp( s, "speak" ) )
	{
		return RESPONSE_SPEAK;
	}
	else if ( !Q_stricmp( s, "response" ) )
	{
		return RESPONSE_RESPONSE;
	}
	else if ( !Q_stricmp( s, "print" ) )
	{
		return RESPONSE_PRINT;
	}

	return RESPONSE_NONE;
}

void CResponseSystem::ParseOneResponse( const char *responseGroupName, ResponseGroup& group )
{
	Response newResponse;
	newResponse.weight.SetFloat( 1.0f );
	AI_ResponseParams *rp = &group.rp;

	newResponse.type = ComputeResponseType( token );
	if ( RESPONSE_NONE == newResponse.type )
	{
		ResponseWarning( "response entry '%s' with unknown response type '%s'\n", responseGroupName, token );
		return;
	}

	ParseToken();
	newResponse.value = CopyString( token );

	while ( TokenWaiting() )
	{
		ParseToken();
		if ( !Q_stricmp( token, "weight" ) )
		{
			ParseToken();
			newResponse.weight.SetFloat( (float)atof( token ) );
			continue;
		}

		if ( !Q_stricmp( token, "predelay" ) )
		{
			ParseToken();
			rp->flags |= AI_ResponseParams::RG_DELAYBEFORESPEAK;
			rp->predelay.FromInterval( ReadInterval( token ) );
			continue;
		}

		if ( !Q_stricmp( token, "nodelay" ) )
		{
			ParseToken();
			rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK;
			rp->delay.start = 0;
			rp->delay.range = 0;
			continue;
		}

		if ( !Q_stricmp( token, "defaultdelay" ) )
		{
			rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK;
			rp->delay.start = AIS_DEF_MIN_DELAY;
			rp->delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY );
			continue;
		}
	
		if ( !Q_stricmp( token, "delay" ) )
		{
			ParseToken();
			rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK;
			rp->delay.FromInterval( ReadInterval( token ) );
			continue;
		}
		
		if ( !Q_stricmp( token, "speakonce" ) )
		{
			rp->flags |= AI_ResponseParams::RG_SPEAKONCE;
			continue;
		}
		
		if ( !Q_stricmp( token, "noscene" ) )
		{
			rp->flags |= AI_ResponseParams::RG_DONT_USE_SCENE;
			continue;
		}

		if ( !Q_stricmp( token, "stop_on_nonidle" ) )
		{
			rp->flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE;
			continue;
		}
		
		if ( !Q_stricmp( token, "odds" ) )
		{
			ParseToken();
			rp->flags |= AI_ResponseParams::RG_ODDS;
			rp->odds = clamp( atoi( token ), 0, 100 );
			continue;
		}
		
		if ( !Q_stricmp( token, "respeakdelay" ) )
		{
			ParseToken();
			rp->flags |= AI_ResponseParams::RG_RESPEAKDELAY;
			rp->respeakdelay.FromInterval( ReadInterval( token ) );
			continue;
		}
		
		if ( !Q_stricmp( token, "weapondelay" ) )
		{
			ParseToken();
			rp->flags |= AI_ResponseParams::RG_WEAPONDELAY;
			rp->weapondelay.FromInterval( ReadInterval( token ) );
			continue;
		}

		if ( !Q_stricmp( token, "soundlevel" ) )
		{
			ParseToken();
			rp->flags |= AI_ResponseParams::RG_SOUNDLEVEL;
			rp->soundlevel = (soundlevel_t)TextToSoundLevel( token );
			continue;
		}

		if ( !Q_stricmp( token, "displayfirst" ) )
		{
			newResponse.first = true;
			group.m_bHasFirst = true;
			continue;
		}

		if ( !Q_stricmp( token, "displaylast" ) )
		{
			newResponse.last = true;
			group.m_bHasLast= true;
			continue;
		}

		ResponseWarning( "response entry '%s' with unknown command '%s'\n", responseGroupName, token );
	}

	group.group.AddToTail( newResponse );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CResponseSystem::IsRootCommand()
{
	if ( !Q_stricmp( token, "#include" ) )
		return true;
	if ( !Q_stricmp( token, "response" ) )
		return true;
	if ( !Q_stricmp( token, "enumeration" ) )
		return true;
	if ( !Q_stricmp( token, "criteria" ) )
		return true;
	if ( !Q_stricmp( token, "criterion" ) )
		return true;
	if ( !Q_stricmp( token, "rule" ) )
		return true;
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *kv - 
//-----------------------------------------------------------------------------
void CResponseSystem::ParseResponse( void )
{
	// Should have groupname at start
	char responseGroupName[ 128 ];

	ResponseGroup newGroup;
	AI_ResponseParams *rp = &newGroup.rp;

	// Response Group Name
	ParseToken();
	Q_strncpy( responseGroupName, token, sizeof( responseGroupName ) );

	while ( 1 )
	{
		ParseToken();

		// Oops, part of next definition
		if( IsRootCommand() )
		{
			Unget();
			break;
		}

		if ( !Q_stricmp( token, "{" ) )
		{
			while ( 1 )
			{
				ParseToken();
				if ( !Q_stricmp( token, "}" ) )
					break;

				if ( !Q_stricmp( token, "permitrepeats" ) )
				{
					newGroup.m_bDepleteBeforeRepeat = false;
					continue;
				}
				else if ( !Q_stricmp( token, "sequential" ) )
				{
					newGroup.SetSequential( true );
					continue;
				}
				else if ( !Q_stricmp( token, "norepeat" ) )
				{
					newGroup.SetNoRepeat( true );
					continue;
				}

				ParseOneResponse( responseGroupName, newGroup );
			}
			break;
		}

		if ( !Q_stricmp( token, "predelay" ) )
		{
			ParseToken();
			rp->flags |= AI_ResponseParams::RG_DELAYBEFORESPEAK;
			rp->predelay.FromInterval( ReadInterval( token ) );
			continue;
		}

		if ( !Q_stricmp( token, "nodelay" ) )
		{
			ParseToken();
			rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK;
			rp->delay.start = 0;
			rp->delay.range = 0;
			continue;
		}

		if ( !Q_stricmp( token, "defaultdelay" ) )
		{
			rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK;
			rp->delay.start = AIS_DEF_MIN_DELAY;
			rp->delay.range = ( AIS_DEF_MAX_DELAY - AIS_DEF_MIN_DELAY );
			continue;
		}
	
		if ( !Q_stricmp( token, "delay" ) )
		{
			ParseToken();
			rp->flags |= AI_ResponseParams::RG_DELAYAFTERSPEAK;
			rp->delay.FromInterval( ReadInterval( token ) );
			continue;
		}
		
		if ( !Q_stricmp( token, "speakonce" ) )
		{
			rp->flags |= AI_ResponseParams::RG_SPEAKONCE;
			continue;
		}
		
		if ( !Q_stricmp( token, "noscene" ) )
		{
			rp->flags |= AI_ResponseParams::RG_DONT_USE_SCENE;
			continue;
		}
		
		if ( !Q_stricmp( token, "stop_on_nonidle" ) )
		{
			rp->flags |= AI_ResponseParams::RG_STOP_ON_NONIDLE;
			continue;
		}

		if ( !Q_stricmp( token, "odds" ) )
		{
			ParseToken();
			rp->flags |= AI_ResponseParams::RG_ODDS;
			rp->odds = clamp( atoi( token ), 0, 100 );
			continue;
		}
		
		if ( !Q_stricmp( token, "respeakdelay" ) )
		{
			ParseToken();
			rp->flags |= AI_ResponseParams::RG_RESPEAKDELAY;
			rp->respeakdelay.FromInterval( ReadInterval( token ) );
			continue;
		}

		if ( !Q_stricmp( token, "weapondelay" ) )
		{
			ParseToken();
			rp->flags |= AI_ResponseParams::RG_WEAPONDELAY;
			rp->weapondelay.FromInterval( ReadInterval( token ) );
			continue;
		}

		if ( !Q_stricmp( token, "soundlevel" ) )
		{
			ParseToken();
			rp->flags |= AI_ResponseParams::RG_SOUNDLEVEL;
			rp->soundlevel = (soundlevel_t)TextToSoundLevel( token );
			continue;
		}

		ParseOneResponse( responseGroupName, newGroup );
	}

	m_Responses.Insert( responseGroupName, newGroup );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *criterion - 
//-----------------------------------------------------------------------------
int CResponseSystem::ParseOneCriterion( const char *criterionName )
{
	char key[ 128 ];
	char value[ 128 ];

	Criteria newCriterion;

	bool gotbody = false;

	while ( TokenWaiting() || !gotbody )
	{
		ParseToken();

		// Oops, part of next definition
		if( IsRootCommand() )
		{
			Unget();
			break;
		}

		if ( !Q_stricmp( token, "{" ) )
		{
			gotbody = true;

			while ( 1 )
			{
				ParseToken();
				if ( !Q_stricmp( token, "}" ) )
					break;

				// Look up subcriteria index
				int idx = m_Criteria.Find( token );
				if ( idx != m_Criteria.InvalidIndex() )
				{
					newCriterion.subcriteria.AddToTail( idx );
				}
				else
				{
					ResponseWarning( "Skipping unrecongized subcriterion '%s' in '%s'\n", token, criterionName );
				}
			}
			continue;
		}
		else if ( !Q_stricmp( token, "required" ) )
		{
			newCriterion.required = true;
		}
		else if ( !Q_stricmp( token, "weight" ) )
		{
			ParseToken();
			newCriterion.weight.SetFloat( (float)atof( token ) );
		}
		else
		{
			Assert( newCriterion.subcriteria.Count() == 0 );

			// Assume it's the math info for a non-subcriteria resposne
			Q_strncpy( key, token, sizeof( key ) );
			ParseToken();
			Q_strncpy( value, token, sizeof( value ) );

			newCriterion.name = CopyString( key );
			newCriterion.value = CopyString( value );

			gotbody = true;
		}
	}

	if ( !newCriterion.IsSubCriteriaType() )
	{
		ComputeMatcher( &newCriterion, newCriterion.matcher );
	}

	if ( m_Criteria.Find( criterionName ) != m_Criteria.InvalidIndex() )
	{
		ResponseWarning( "Multiple definitions for criteria '%s'\n", criterionName );
		return m_Criteria.InvalidIndex();
	}

	int idx = m_Criteria.Insert( criterionName, newCriterion );
	return idx;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *kv - 
//-----------------------------------------------------------------------------
void CResponseSystem::ParseCriterion( void )
{
	// Should have groupname at start
	char criterionName[ 128 ];
	ParseToken();
	Q_strncpy( criterionName, token, sizeof( criterionName ) );

	ParseOneCriterion( criterionName );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *kv - 
//-----------------------------------------------------------------------------
void CResponseSystem::ParseEnumeration( void )
{
	char enumerationName[ 128 ];
	ParseToken();
	Q_strncpy( enumerationName, token, sizeof( enumerationName ) );

	ParseToken();
	if ( Q_stricmp( token, "{" ) )
	{
		ResponseWarning( "Expecting '{' in enumeration '%s', got '%s'\n", enumerationName, token );
		return;
	}

	while ( 1 )
	{
		ParseToken();
		if ( !Q_stricmp( token, "}" ) )
			break;

		if ( Q_strlen( token ) <= 0 )
		{
			ResponseWarning( "Expecting more tokens in enumeration '%s'\n", enumerationName );
			break;
		}

		char key[ 128 ];

		Q_strncpy( key, token, sizeof( key ) );
		ParseToken();
		float value = (float)atof( token );

		char sz[ 128 ];
		Q_snprintf( sz, sizeof( sz ), "[%s::%s]", enumerationName, key );
		Q_strlower( sz );

		Enumeration newEnum;
		newEnum.value = value;

		if ( m_Enumerations.Find( sz ) == m_Enumerations.InvalidIndex() )
		{
			m_Enumerations.Insert( sz, newEnum );
		}
		/*
		else
		{
			ResponseWarning( "Ignoring duplication enumeration '%s'\n", sz );
		}
		*/
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *kv - 
//-----------------------------------------------------------------------------
void CResponseSystem::ParseRule( void )
{
	static int instancedCriteria = 0;

	char ruleName[ 128 ];
	ParseToken();
	Q_strncpy( ruleName, token, sizeof( ruleName ) );

	ParseToken();
	if ( Q_stricmp( token, "{" ) )
	{
		ResponseWarning( "Expecting '{' in rule '%s', got '%s'\n", ruleName, token );
		return;
	}

	// entries are "criteria", "response" or an in-line criteria to instance
	Rule newRule;

	char sz[ 128 ];

	bool validRule = true;
	while ( 1 )
	{
		ParseToken();
		if ( !Q_stricmp( token, "}" ) )
		{
			break;
		}

		if ( Q_strlen( token ) <= 0 )
		{
			ResponseWarning( "Expecting more tokens in rule '%s'\n", ruleName );
			break;
		}

		if ( !Q_stricmp( token, "matchonce" ) )
		{
			newRule.m_bMatchOnce = true;
			continue;
		}

		if ( !Q_stricmp( token, "applyContextToWorld" ) )
		{
			newRule.m_bApplyContextToWorld = true;
			continue;
		}

		if ( !Q_stricmp( token, "applyContext" ) )
		{
			ParseToken();
			if ( newRule.GetContext() == NULL )
			{
				newRule.SetContext( token );
			}
			else
			{
				CFmtStrN<1024> newContext( "%s,%s", newRule.GetContext(), token );
				newRule.SetContext( newContext );
			}
			continue;
		}
		
		if ( !Q_stricmp( token, "response" ) )
		{
			// Read them until we run out.
			while ( TokenWaiting() )
			{
				ParseToken();
				int idx = m_Responses.Find( token );
				if ( idx != m_Responses.InvalidIndex() )
				{
					MEM_ALLOC_CREDIT();
					newRule.m_Responses.AddToTail( idx );
				}
				else
				{
					validRule = false;
					ResponseWarning( "No such response '%s' for rule '%s'\n", token, ruleName );
				}
			}
			continue;
		}

		if ( !Q_stricmp( token, "criteria" ) ||
			 !Q_stricmp( token, "criterion" ) )
		{
			// Read them until we run out.
			while ( TokenWaiting() )
			{
				ParseToken();

				int idx = m_Criteria.Find( token );
				if ( idx != m_Criteria.InvalidIndex() )
				{
					MEM_ALLOC_CREDIT();
					newRule.m_Criteria.AddToTail( idx );
				}
				else
				{
					validRule = false;
					ResponseWarning( "No such criterion '%s' for rule '%s'\n", token, ruleName );
				}
			}
			continue;
		}

		// It's an inline criteria, generate a name and parse it in
		Q_snprintf( sz, sizeof( sz ), "[%s%03i]", ruleName, ++instancedCriteria );
		Unget();
		int idx = ParseOneCriterion( sz );
		if ( idx != m_Criteria.InvalidIndex() )
		{
			newRule.m_Criteria.AddToTail( idx );
		}
	}

	if ( validRule )
	{
		m_Rules.Insert( ruleName, newRule );
	}
	else
	{
		DevMsg( "Discarded rule %s\n", ruleName );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int	CResponseSystem::GetCurrentToken() const
{
	if ( m_ScriptStack.Count() <= 0 )
		return -1;
	
	return m_ScriptStack[ 0 ].tokencount;
}


void CResponseSystem::ResponseWarning( const char *fmt, ... )
{
	va_list		argptr;
#ifndef _XBOX
	static char	string[1024];
#else
	char		string[1024];
#endif	

	va_start (argptr, fmt);
	Q_vsnprintf(string, sizeof(string), fmt,argptr);
	va_end (argptr);

	char cur[ 256 ];
	GetCurrentScript( cur, sizeof( cur ) );
	DevMsg( 1, "%s(token %i) : %s", cur, GetCurrentToken(), string );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CResponseSystem::CopyCriteriaFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem )
{
	// Add criteria from this rule to global list in custom response system.
	int nCriteriaCount = pSrcRule->m_Criteria.Count();
	for ( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria )
	{
		int iSrcIndex = pSrcRule->m_Criteria[iCriteria];
		Criteria *pSrcCriteria = &m_Criteria[iSrcIndex];
		if ( pSrcCriteria )
		{
			int iIndex = pCustomSystem->m_Criteria.Find( m_Criteria.GetElementName( iSrcIndex ) );
			if ( iIndex != pCustomSystem->m_Criteria.InvalidIndex() )
			{
				pDstRule->m_Criteria.AddToTail( iIndex );
				continue;
			}

			// Add the criteria.
			Criteria dstCriteria;

			dstCriteria.name = CopyString( pSrcCriteria->name );
			dstCriteria.value = CopyString( pSrcCriteria->value );
			dstCriteria.weight = pSrcCriteria->weight;
			dstCriteria.required = pSrcCriteria->required;
			dstCriteria.matcher = pSrcCriteria->matcher;

			int nSubCriteriaCount = pSrcCriteria->subcriteria.Count();
			for ( int iSubCriteria = 0; iSubCriteria < nSubCriteriaCount; ++iSubCriteria )
			{
				int iSrcSubIndex = pSrcCriteria->subcriteria[iSubCriteria];
				Criteria *pSrcSubCriteria = &m_Criteria[iSrcSubIndex];
				if ( pSrcCriteria )
				{
					int iSubIndex = pCustomSystem->m_Criteria.Find( pSrcSubCriteria->value );
					if ( iSubIndex != pCustomSystem->m_Criteria.InvalidIndex() )
						continue;

					// Add the criteria.
					Criteria dstSubCriteria;

					dstSubCriteria.name = CopyString( pSrcSubCriteria->name );
					dstSubCriteria.value = CopyString( pSrcSubCriteria->value );
					dstSubCriteria.weight = pSrcSubCriteria->weight;
					dstSubCriteria.required = pSrcSubCriteria->required;
					dstSubCriteria.matcher = pSrcSubCriteria->matcher;

					int iSubInsertIndex = pCustomSystem->m_Criteria.Insert( pSrcSubCriteria->value, dstSubCriteria );
					dstCriteria.subcriteria.AddToTail( iSubInsertIndex );
				}
			}

			int iInsertIndex = pCustomSystem->m_Criteria.Insert( m_Criteria.GetElementName( iSrcIndex ), dstCriteria );
			pDstRule->m_Criteria.AddToTail( iInsertIndex );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CResponseSystem::CopyResponsesFrom( Rule *pSrcRule, Rule *pDstRule, CResponseSystem *pCustomSystem )
{
	// Add responses from this rule to global list in custom response system.
	int nResponseGroupCount = pSrcRule->m_Responses.Count();
	for ( int iResponseGroup = 0; iResponseGroup < nResponseGroupCount; ++iResponseGroup )
	{
		int iSrcResponseGroup = pSrcRule->m_Responses[iResponseGroup];
		ResponseGroup *pSrcResponseGroup = &m_Responses[iSrcResponseGroup];
		if ( pSrcResponseGroup )
		{
			// Add response group.			
			ResponseGroup dstResponseGroup;

			dstResponseGroup.rp = pSrcResponseGroup->rp;
			dstResponseGroup.m_bDepleteBeforeRepeat = pSrcResponseGroup->m_bDepleteBeforeRepeat;
			dstResponseGroup.m_nDepletionCount = pSrcResponseGroup->m_nDepletionCount;
			dstResponseGroup.m_bHasFirst = pSrcResponseGroup->m_bHasFirst;
			dstResponseGroup.m_bHasLast = pSrcResponseGroup->m_bHasLast;
			dstResponseGroup.m_bSequential = pSrcResponseGroup->m_bSequential;
			dstResponseGroup.m_bNoRepeat = pSrcResponseGroup->m_bNoRepeat;
			dstResponseGroup.m_bEnabled = pSrcResponseGroup->m_bEnabled;
			dstResponseGroup.m_nCurrentIndex = pSrcResponseGroup->m_nCurrentIndex;

			int nSrcResponseCount = pSrcResponseGroup->group.Count();
			for ( int iResponse = 0; iResponse < nSrcResponseCount; ++iResponse )
			{
				Response *pSrcResponse = &pSrcResponseGroup->group[iResponse];
				if ( pSrcResponse )
				{
					// Add Response
					Response dstResponse;

					dstResponse.weight = pSrcResponse->weight;
					dstResponse.type = pSrcResponse->type;
					dstResponse.value = CopyString( pSrcResponse->value );
					dstResponse.depletioncount = pSrcResponse->depletioncount;
					dstResponse.first = pSrcResponse->first;
					dstResponse.last = pSrcResponse->last;

					dstResponseGroup.group.AddToTail( dstResponse );
				}
			}

			int iInsertIndex = pCustomSystem->m_Responses.Insert( m_Responses.GetElementName( iSrcResponseGroup ), dstResponseGroup );
			pDstRule->m_Responses.AddToTail( iInsertIndex );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CResponseSystem::CopyEnumerationsFrom( CResponseSystem *pCustomSystem )
{
	int nEnumerationCount = m_Enumerations.Count();
	for ( int iEnumeration = 0; iEnumeration < nEnumerationCount; ++iEnumeration )
	{
		Enumeration *pSrcEnumeration = &m_Enumerations[iEnumeration];
		if ( pSrcEnumeration )
		{
			Enumeration dstEnumeration;
			dstEnumeration.value = pSrcEnumeration->value;
			pCustomSystem->m_Enumerations.Insert( m_Enumerations.GetElementName( iEnumeration ), dstEnumeration );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CResponseSystem::CopyRuleFrom( Rule *pSrcRule, int iRule, CResponseSystem *pCustomSystem )
{
	// Verify data.
	Assert( pSrcRule );
	Assert( pCustomSystem );
	if ( !pSrcRule || !pCustomSystem )
		return;

	// New rule
	Rule dstRule;

	dstRule.SetContext( pSrcRule->GetContext() );
	dstRule.m_bMatchOnce = pSrcRule->m_bMatchOnce;
	dstRule.m_bEnabled = pSrcRule->m_bEnabled;
	dstRule.m_bApplyContextToWorld = pSrcRule->m_bApplyContextToWorld;

	// Copy off criteria.
	CopyCriteriaFrom( pSrcRule, &dstRule, pCustomSystem );

	// Copy off responses.
	CopyResponsesFrom( pSrcRule, &dstRule, pCustomSystem );

	// Copy off enumerations - Don't think we use these.
//	CopyEnumerationsFrom( pCustomSystem );

	// Add rule.
	pCustomSystem->m_Rules.Insert( m_Rules.GetElementName( iRule ), dstRule );
}

//-----------------------------------------------------------------------------
// Purpose: A special purpose response system associated with a custom entity
//-----------------------------------------------------------------------------
class CInstancedResponseSystem : public CResponseSystem
{
	typedef CResponseSystem BaseClass;

public:
	CInstancedResponseSystem( const char *scriptfile ) :
	  m_pszScriptFile( 0 )
	{
		Assert( scriptfile );

		int len = Q_strlen( scriptfile ) + 1;
		m_pszScriptFile = new char[ len ];
		Assert( m_pszScriptFile );
		Q_strncpy( m_pszScriptFile, scriptfile, len );
	}

	~CInstancedResponseSystem()
	{
		delete[] m_pszScriptFile;
	}
	virtual const char *GetScriptFile( void ) 
	{
		Assert( m_pszScriptFile );
		return m_pszScriptFile;
	}

	// CAutoGameSystem
	virtual bool Init()
	{
		const char *basescript = GetScriptFile();
		LoadRuleSet( basescript );
		return true;
	}

	virtual void LevelInitPostEntity()
	{
		ResetResponseGroups();
	}

	virtual void Release()
	{
		Clear();
		delete this;
	}
private:

	char *m_pszScriptFile;
};

//-----------------------------------------------------------------------------
// Purpose: The default response system for expressive AIs
//-----------------------------------------------------------------------------
class CDefaultResponseSystem : public CResponseSystem, public CAutoGameSystem
{
	typedef CAutoGameSystem BaseClass;

public:
	CDefaultResponseSystem() : CAutoGameSystem( "CDefaultResponseSystem" )
	{
	}

	virtual const char *GetScriptFile( void ) 
	{
		return "scripts/talker/response_rules.txt";
	}

	// CAutoServerSystem
	virtual bool Init();
	virtual void Shutdown();

	virtual void LevelInitPostEntity()
	{
	}

	virtual void Release()
	{
		Assert( 0 );
	}

	void AddInstancedResponseSystem( const char *scriptfile, CInstancedResponseSystem *sys )
	{
		m_InstancedSystems.Insert( scriptfile, sys );
	}

	CInstancedResponseSystem *FindResponseSystem( const char *scriptfile )
	{
		int idx = m_InstancedSystems.Find( scriptfile );
		if ( idx == m_InstancedSystems.InvalidIndex() )
			return NULL;
		return m_InstancedSystems[ idx ];
	}

	IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile )
	{
		CInstancedResponseSystem *sys = ( CInstancedResponseSystem * )FindResponseSystem( scriptfile );
		if ( !sys )
		{
			sys = new CInstancedResponseSystem( scriptfile );
			if ( !sys )
			{
				Error( "Failed to load response system data from %s", scriptfile );
			}

			if ( !sys->Init() )
			{
				Error( "CInstancedResponseSystem:  Failed to init response system from %s!", scriptfile );
			}

			AddInstancedResponseSystem( scriptfile, sys );
		}

		sys->Precache();

		return ( IResponseSystem * )sys;
	}

	IResponseSystem *BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore );
	void DestroyCustomResponseSystems();

	virtual void LevelInitPreEntity()
	{
		// This will precache the default system
		// All user installed systems are init'd by PrecacheCustomResponseSystem which will call sys->Precache() on the ones being used

		// FIXME:  This is SLOW the first time you run the engine (can take 3 - 10 seconds!!!)
		if ( ShouldPrecache() )
		{
			Precache();
		}

		ResetResponseGroups();
	}

	void ReloadAllResponseSystems()
	{
		Clear();
		Init();

		int c = m_InstancedSystems.Count();
		for ( int i = c - 1 ; i >= 0; i-- )
		{
			CInstancedResponseSystem *sys = m_InstancedSystems[ i ];
			if ( !IsCustomManagable() )
			{
				sys->Clear();
				sys->Init();
			}
			else
			{
				// Custom reponse rules will manage/reload themselves - remove them.
				m_InstancedSystems.RemoveAt( i );
			}
		}

	}

private:

	void ClearInstanced()
	{
		int c = m_InstancedSystems.Count();
		for ( int i = c - 1 ; i >= 0; i-- )
		{
			CInstancedResponseSystem *sys = m_InstancedSystems[ i ];
			sys->Release();
		}
		m_InstancedSystems.RemoveAll();
	}

	CUtlDict< CInstancedResponseSystem *, int > m_InstancedSystems;
};

IResponseSystem *CDefaultResponseSystem::BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore )
{
	// Create a instanced response system. 
	CInstancedResponseSystem *pCustomSystem = new CInstancedResponseSystem( pszCustomName );
	if ( !pCustomSystem )
	{
		Error( "BuildCustomResponseSystemGivenCriterea: Failed to create custom response system %s!", pszCustomName );
	}

	pCustomSystem->Clear();

	// Copy the relevant rules and data.
	int nRuleCount = m_Rules.Count();
	for ( int iRule = 0; iRule < nRuleCount; ++iRule )
	{
		Rule *pRule = &m_Rules[iRule];
		if ( pRule )
		{
			float flScore = 0.0f;

			int nCriteriaCount = pRule->m_Criteria.Count();
			for ( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria )
			{
				int iRuleCriteria = pRule->m_Criteria[iCriteria];

				flScore += LookForCriteria( criteriaSet, iRuleCriteria );
				if ( flScore >= flCriteriaScore )
				{
					CopyRuleFrom( pRule, iRule, pCustomSystem );
					break;
				}
			}
		}
	}

	// Set as a custom response system.
	m_bCustomManagable = true;
	AddInstancedResponseSystem( pszCustomName, pCustomSystem );

//	pCustomSystem->DumpDictionary( pszCustomName );

	return pCustomSystem;
}

void CDefaultResponseSystem::DestroyCustomResponseSystems()
{
	ClearInstanced();
}


static CDefaultResponseSystem defaultresponsesytem;
IResponseSystem *g_pResponseSystem = &defaultresponsesytem;

CON_COMMAND( rr_reloadresponsesystems, "Reload all response system scripts." )
{
	defaultresponsesytem.ReloadAllResponseSystems();

#if defined( TF_DLL )
	// This is kind of hacky, but I need to get it in for now!
	if( g_pGameRules->IsMultiplayer() )
	{
		CMultiplayRules *pMultiplayRules = static_cast<CMultiplayRules*>( g_pGameRules );
		pMultiplayRules->InitCustomResponseRulesDicts();
	}
#endif
}

static short RESPONSESYSTEM_SAVE_RESTORE_VERSION = 1;

// note:  this won't save/restore settings from instanced response systems.  Could add that with a CDefSaveRestoreOps implementation if needed
// 
class CDefaultResponseSystemSaveRestoreBlockHandler : public CDefSaveRestoreBlockHandler
{
public:
	const char *GetBlockName()
	{
		return "ResponseSystem";
	}

	void WriteSaveHeaders( ISave *pSave )
	{
		pSave->WriteShort( &RESPONSESYSTEM_SAVE_RESTORE_VERSION );
	}
	
	void ReadRestoreHeaders( IRestore *pRestore )
	{
		// No reason why any future version shouldn't try to retain backward compatability. The default here is to not do so.
		short version;
		pRestore->ReadShort( &version );
		m_fDoLoad = ( version == RESPONSESYSTEM_SAVE_RESTORE_VERSION );
	}

	void Save( ISave *pSave )
	{
		CDefaultResponseSystem& rs = defaultresponsesytem;

		int count = rs.m_Responses.Count();
		pSave->WriteInt( &count );
		for ( int i = 0; i < count; ++i )
		{
			pSave->StartBlock( "ResponseGroup" );

			pSave->WriteString( rs.m_Responses.GetElementName( i ) );
			const ResponseGroup *group = &rs.m_Responses[ i ];
			pSave->WriteAll( group );

			short groupCount = group->group.Count();
			pSave->WriteShort( &groupCount );
			for ( int j = 0; j < groupCount; ++j )
			{
				const Response *response = &group->group[ j ];
				pSave->StartBlock( "Response" );
				pSave->WriteString( response->value );
				pSave->WriteAll( response );
				pSave->EndBlock();
			}

			pSave->EndBlock();
		}
	}

	void Restore( IRestore *pRestore, bool createPlayers )
	{
		if ( !m_fDoLoad )
			return;

		CDefaultResponseSystem& rs = defaultresponsesytem;

		int count = pRestore->ReadInt();
		for ( int i = 0; i < count; ++i )
		{
			char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF];
			pRestore->StartBlock( szResponseGroupBlockName );
			if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) )
			{

				char groupname[ 256 ];
				pRestore->ReadString( groupname, sizeof( groupname ), 0 );

				// Try and find it
				int idx = rs.m_Responses.Find( groupname );
				if ( idx != rs.m_Responses.InvalidIndex() )
				{
					ResponseGroup *group = &rs.m_Responses[ idx ];
					pRestore->ReadAll( group );

					short groupCount = pRestore->ReadShort();
					for ( int j = 0; j < groupCount; ++j )
					{
						char szResponseBlockName[SIZE_BLOCK_NAME_BUF];

						char responsename[ 256 ];
						pRestore->StartBlock( szResponseBlockName );
						if ( !Q_stricmp( szResponseBlockName, "Response" ) )
						{
							pRestore->ReadString( responsename, sizeof( responsename ), 0 );

							// Find it by name
							int ri;
							for ( ri = 0; ri < group->group.Count(); ++ri )
							{
								Response *response = &group->group[ ri ];
								if ( !Q_stricmp( response->value, responsename ) )
								{
									break;
								}
							}

							if ( ri < group->group.Count() )
							{
								Response *response = &group->group[ ri ];
								pRestore->ReadAll( response );
							}
						}

						pRestore->EndBlock();
					}
				}
			}

			pRestore->EndBlock();
		}
	}
private:

	bool		m_fDoLoad;

} g_DefaultResponseSystemSaveRestoreBlockHandler;
	
ISaveRestoreBlockHandler *GetDefaultResponseSystemSaveRestoreBlockHandler()
{
	return &g_DefaultResponseSystemSaveRestoreBlockHandler;
}

//-----------------------------------------------------------------------------
// CResponseSystemSaveRestoreOps
//
// Purpose: Handles save and load for instanced response systems...
//
// BUGBUG:  This will save the same response system to file multiple times for "shared" response systems and 
//  therefore it'll restore the same data onto the same pointer N times on reload (probably benign for now, but we could
//  write code to save/restore the instanced ones by filename in the block handler above maybe?
//-----------------------------------------------------------------------------

class CResponseSystemSaveRestoreOps : public CDefSaveRestoreOps
{
public:

	virtual void Save( const SaveRestoreFieldInfo_t &fieldInfo, ISave *pSave )
	{
		CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField;
		if ( !pRS || pRS == &defaultresponsesytem )
			return;
		
		int count = pRS->m_Responses.Count();
		pSave->WriteInt( &count );
		for ( int i = 0; i < count; ++i )
		{
			pSave->StartBlock( "ResponseGroup" );

			pSave->WriteString( pRS->m_Responses.GetElementName( i ) );
			const ResponseGroup *group = &pRS->m_Responses[ i ];
			pSave->WriteAll( group );

			short groupCount = group->group.Count();
			pSave->WriteShort( &groupCount );
			for ( int j = 0; j < groupCount; ++j )
			{
				const Response *response = &group->group[ j ];
				pSave->StartBlock( "Response" );
				pSave->WriteString( response->value );
				pSave->WriteAll( response );
				pSave->EndBlock();
			}

			pSave->EndBlock();
		}
	}
	
	virtual void Restore( const SaveRestoreFieldInfo_t &fieldInfo, IRestore *pRestore )
	{
		CResponseSystem *pRS = *(CResponseSystem **)fieldInfo.pField;
		if ( !pRS || pRS == &defaultresponsesytem )
			return;

		int count = pRestore->ReadInt();
		for ( int i = 0; i < count; ++i )
		{
			char szResponseGroupBlockName[SIZE_BLOCK_NAME_BUF];
			pRestore->StartBlock( szResponseGroupBlockName );
			if ( !Q_stricmp( szResponseGroupBlockName, "ResponseGroup" ) )
			{

				char groupname[ 256 ];
				pRestore->ReadString( groupname, sizeof( groupname ), 0 );

				// Try and find it
				int idx = pRS->m_Responses.Find( groupname );
				if ( idx != pRS->m_Responses.InvalidIndex() )
				{
					ResponseGroup *group = &pRS->m_Responses[ idx ];
					pRestore->ReadAll( group );

					short groupCount = pRestore->ReadShort();
					for ( int j = 0; j < groupCount; ++j )
					{
						char szResponseBlockName[SIZE_BLOCK_NAME_BUF];

						char responsename[ 256 ];
						pRestore->StartBlock( szResponseBlockName );
						if ( !Q_stricmp( szResponseBlockName, "Response" ) )
						{
							pRestore->ReadString( responsename, sizeof( responsename ), 0 );

							// Find it by name
							int ri;
							for ( ri = 0; ri < group->group.Count(); ++ri )
							{
								Response *response = &group->group[ ri ];
								if ( !Q_stricmp( response->value, responsename ) )
								{
									break;
								}
							}

							if ( ri < group->group.Count() )
							{
								Response *response = &group->group[ ri ];
								pRestore->ReadAll( response );
							}
						}

						pRestore->EndBlock();
					}
				}
			}

			pRestore->EndBlock();
		}
	}
	
} g_ResponseSystemSaveRestoreOps;

ISaveRestoreOps *responseSystemSaveRestoreOps = &g_ResponseSystemSaveRestoreOps;

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CDefaultResponseSystem::Init()
{
/*
	Warning( "sizeof( Response ) == %d\n", sizeof( Response ) );
	Warning( "sizeof( ResponseGroup ) == %d\n", sizeof( ResponseGroup ) );
	Warning( "sizeof( Criteria ) == %d\n", sizeof( Criteria ) );
	Warning( "sizeof( AI_ResponseParams ) == %d\n", sizeof( AI_ResponseParams ) );
*/
	const char *basescript = GetScriptFile();

	LoadRuleSet( basescript );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CDefaultResponseSystem::Shutdown()
{
	// Wipe instanced versions
	ClearInstanced();

	// Clear outselves
	Clear();
	// IServerSystem chain
	BaseClass::Shutdown();
}

//-----------------------------------------------------------------------------
// Purpose: Instance a custom response system
// Input  : *scriptfile - 
// Output : IResponseSystem
//-----------------------------------------------------------------------------
IResponseSystem *PrecacheCustomResponseSystem( const char *scriptfile )
{
	return defaultresponsesytem.PrecacheCustomResponseSystem( scriptfile );
}

//-----------------------------------------------------------------------------
// Purpose: Instance a custom response system
// Input  : *scriptfile -
//			set - 
// Output : IResponseSystem
//-----------------------------------------------------------------------------
IResponseSystem *BuildCustomResponseSystemGivenCriteria( const char *pszBaseFile, const char *pszCustomName, AI_CriteriaSet &criteriaSet, float flCriteriaScore )
{
	return defaultresponsesytem.BuildCustomResponseSystemGivenCriteria( pszBaseFile, pszCustomName, criteriaSet, flCriteriaScore );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void DestroyCustomResponseSystems()
{
	defaultresponsesytem.DestroyCustomResponseSystems();
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CResponseSystem::DumpRules()
{
	int c = m_Rules.Count();
	int i;

	for ( i = 0; i < c; i++ )
	{
		Msg("%s\n", m_Rules.GetElementName( i ) );
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CResponseSystem::DumpDictionary( const char *pszName )
{
	Msg( "\nDictionary: %s\n", pszName );

	int nRuleCount = m_Rules.Count();
	for ( int iRule = 0; iRule < nRuleCount; ++iRule )
	{
		Msg("	Rule %d: %s\n", iRule, m_Rules.GetElementName( iRule ) );

		Rule *pRule = &m_Rules[iRule];

		int nCriteriaCount = pRule->m_Criteria.Count();
		for( int iCriteria = 0; iCriteria < nCriteriaCount; ++iCriteria )
		{
			int iRuleCriteria = pRule->m_Criteria[iCriteria];
			Criteria *pCriteria = &m_Criteria[iRuleCriteria];
			Msg( "		Criteria %d: %s %s\n", iCriteria, pCriteria->name, pCriteria->value );
		}

		int nResponseGroupCount = pRule->m_Responses.Count();
		for ( int iResponseGroup = 0; iResponseGroup < nResponseGroupCount; ++iResponseGroup )
		{
			int iRuleResponse = pRule->m_Responses[iResponseGroup];
			ResponseGroup *pResponseGroup = &m_Responses[iRuleResponse];

			Msg( "		ResponseGroup %d: %s\n", iResponseGroup, m_Responses.GetElementName( iRuleResponse ) );

			int nResponseCount = pResponseGroup->group.Count();
			for ( int iResponse = 0; iResponse < nResponseCount; ++iResponse )
			{
				Response *pResponse = &pResponseGroup->group[iResponse];
				Msg( "			Response %d: %s\n", iResponse, pResponse->value );
			}
		}
	}
}
