//-----------------------------------------------------------------------------
//
//  $Logfile:: /Quake 2 Engine/Sin/code/game/listener.cpp                     $
// $Revision:: 48                                                             $
//   $Author:: Aldie                                                          $
//     $Date:: 3/26/99 6:26p                                                  $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// $Log:: /Quake 2 Engine/Sin/code/game/listener.cpp                          $
//
// 48    3/26/99 6:26p Aldie
// Fixed more CTF bugs, probably the last ones
//
// 47    3/18/99 6:44p Aldie
// CancelPendingEvents when destructing
//
// 46    3/17/99 4:00p Aldie
// CTF Update
//
// 45    11/10/98 5:48p Jimdose
// Made SortEventList sort the list manually when TEMPLATE_EXPORT is not
// defined
//
// 44    10/26/98 4:27p Jimdose
// Sped up ValidEvent
// Added FindEvent( const char * )
//
// 43    10/24/98 3:25a Jimdose
// added g_watch for only displaying events from a specific object
//
// 42    10/19/98 6:30p Jimdose
// increased g_eventlimit to 1500
//
// 41    10/19/98 12:07a Jimdose
// made all code use fast checks for inheritance (no text lookups when
// possible)
// isSubclassOf no longer requires ::_classinfo()
//
// 40    10/18/98 8:44p Jimdose
// Made commandList a Container of str * instead of str.  Fixes problem of name
// pointer in event being invalidated when commandList is resized
//
// 39    10/18/98 3:22a Jimdose
// Added code for timing events
//
// 38    10/17/98 11:33p Jimdose
// Added the event name to Event to help with debugging
//
// 37    10/17/98 12:22a Jimdose
// upped g_eventlimit to 500
//
// 36    10/16/98 7:19p Jimdose
// Added g_eventlimit
// G_ProcessPendingEvents now breaks out if the number of events executed
// during the call exceeds g_eventlimit.
// Removed g_response and old code from ProcessEvent
//
// 35    10/16/98 1:53a Jimdose
// Added FL_DONTSAVE to entities, so events that belong to entities with this
// flag aren't archived
//
// 34    10/10/98 1:28a Jimdose
// PostEvent kills any events posted during a loadgame
//
// 33    10/07/98 11:52p Jimdose
// Wrote Event archiving functions
//
// 32    9/24/98 1:46a Jimdose
// Made Event a subclass of Class
//
// 31    9/19/98 4:32p Jimdose
// Added ListCommands
//
// 30    8/27/98 9:03p Jimdose
// Moved a lot of small functions to the header as inline
//
// 29    8/26/98 6:42p Jimdose
// Added more info when g_showevents is set
//
// 28    8/20/98 4:42p Jimdose
// (Finally) added binary search for resolving event names
//
// 27    8/17/98 4:29p Jimdose
// added event reporting with g_showevents cvar
//
// 26    8/06/98 5:18p Jimdose
// Fixed check for default flags in Event constructor for str objects
//
// 25    8/01/98 7:58p Jimdose
// Fixed bug with cheats in dm
//
// 24    7/31/98 8:09p Jimdose
// Script commands now include flags to indicate cheats and console commands
//
// 23    7/31/98 4:19p Jimdose
// Added DepthOfEvent
//
// 22    7/20/98 5:09p Jimdose
// Added ProcessPendingEvents
//
// 21    7/10/98 10:00p Jimdose
// Made remove script command post remove event so that it doesn't cause
// problems during event callbacks which trigger the script
//
// 20    7/08/98 12:54p Jimdose
// Made error for vectors not including '(' ')' a developer 2 warning
//
// 19    6/30/98 6:05p Jimdose
// Added IsVectorAt, IsEntityAt, and IsNumericAt for doing type checking on
// args
// Changed format for storing vectors to make it easy to identify
//
// 18    6/27/98 9:18p Jimdose
// Made lookup for event responses for faster processing
//
// 17    6/24/98 6:48p Jimdose
// Made entity number based commands use "*" prefix consistantly
//
// 16    6/20/98 7:41p Jimdose
// Made GetEntity return NULL if the index is out of range
//
// 15    6/18/98 8:47p Jimdose
// Added better event error handling
// Added source info to events
// Added checks for event still in use when processing events (prevents
// deleting events twice)
//
// 14    6/17/98 3:03p Markd
// Changed NumArgs back to previous behavior
//
// 13    6/10/98 7:53p Markd
// Made NumArgs behave correctly like argc
//
// 12    5/24/98 8:46p Jimdose
// Made a lot of functions more str-friendly.
// Got rid of a lot of char * based strings
// Cleaned up get spawn arg functions and sound functions
// sound functions now use consistant syntax
//
// 11    5/24/98 4:48p Jimdose
// Made char *'s const
//
// 10    5/24/98 1:06a Jimdose
// added const to various char *s
//
// 9     5/08/98 2:51p Jimdose
// Moved archiving functions up to Class
//
// 8     5/07/98 10:42p Jimdose
// Added archive and unarchive
//
// 7     4/30/98 9:24p Jimdose
// Changed use of string to str class
//
// 6     4/06/98 5:44p Jimdose
// Added assertion for null entities in AddEntity.  In the release version, it
// "safely" treats null ents as entnum 0
//
// 5     4/02/98 4:48p Jimdose
// Added initCommandList to ensure that NullEvent is always initialized first
//
// 4     3/24/98 5:02p Jimdose
// Made GetToken return the exact string instead of resolving the value of
// variables
// PostponeEvent was not updating the position of the event in the list, which
// would cause a logjam of events that were following the event.
//
// 3     3/23/98 1:31p Jimdose
// Revamped event and command system
//
// 2     3/04/98 1:01p Jimdose
// Created file
//
// DESCRIPTION:
//

#ifdef GAME_HARD_LINKED /* FS: FIXME: VS2005 flips out about some of the extern "C" CVARs but GCC is OK with it... Ugh. */
#include "qcommon.h"
#include "console3d.h"
#endif

#include "listener.h"
#include "scriptvariable.h"
#include "worldspawn.h"
#include "scriptmaster.h"

Event EV_Remove( "immediateremove" );
Event EV_ScriptRemove( "remove" );

typedef struct eventcache_s
{
	Listener* obj;
	Event*      event;
	float time;

	struct eventcache_s* next;
	struct eventcache_s* prev;
} eventcache_t;

#define MAX_EVENTS 2000

extern "C"
{
	eventcache_t Events[ MAX_EVENTS ];
	int numEvents = 0;
	cvar_t*         g_numevents;
};

cvar_t* g_showevents;
cvar_t* g_eventlimit;
cvar_t* g_timeevents;
cvar_t* g_watch;

eventcache_t FreeEventHead;
eventcache_t* FreeEvents = &FreeEventHead;
eventcache_t EventQueueHead;
eventcache_t* EventQueue = &EventQueueHead;

Container<str*>* Event::commandList = NULL;
Container<int>* Event::flagList = NULL;
Container<int>* Event::sortedList = NULL;
qboolean Event::dirtylist = false;

Event NullEvent;

CLASS_DECLARATION( Class, Event, NULL );

ResponseDef Event::Responses[] =
{
	{ NULL, NULL }
};

int Event::NumEventCommands( void )
{
	if( commandList )
	{
		// Add 1 since container gives the inclusive number of objects
		return commandList->NumObjects() + 1;
	}

	return 0;
}

int Event::compareEvents( const void* arg1, const void* arg2 )
{
	int ev1;
	int ev2;

	ev1 = *( int* )arg1;
	ev2 = *( int* )arg2;

	return stricmp( commandList->ObjectAt( ev1 )->c_str(), commandList->ObjectAt( ev2 )->c_str() );
}

void Event::SortEventList( void )
{
	dirtylist = false;

	if( sortedList && commandList )
	{
#ifndef EXPORT_TEMPLATE
		qsort( ( void* )sortedList->AddressOfObjectAt( 1 ),
			   ( size_t )sortedList->NumObjects(),
			   sizeof( int ), compareEvents );
#else
		sortedList->Sort( compareEvents );
#endif
	}
}

inline int Event::FindEvent( const char* name )
{
	int eventnum;
	int index;
	int l;
	int r;
	int diff;

	assert( name );
	if( !name )
	{
		return 0;
	}

	if( !commandList )
	{
		return 0;
	}

	if( dirtylist )
	{
		SortEventList();
	}

	l = 1;
	r = sortedList->NumObjects();
	while( r >= l )
	{
		index = ( l + r ) >> 1;
		eventnum = sortedList->ObjectAt( index );
		diff = stricmp( name, commandList->ObjectAt( eventnum )->c_str() );
		if( diff < 0 )
		{
			r = index - 1;
		}
		else if( diff > 0 )
		{
			l = index + 1;
		}
		else
		{
			return eventnum;
		}
	}

	return 0;
}

int Event::FindEvent( str& name )
{
	return FindEvent( name.c_str() );
}

void Event::ListCommands( const char* mask )
{
	str name;
	int flags;
	int eventnum;
	int num;
	int i;
	int n;
	int l;
	int p;
	int hidden;
	str text;

	if( !commandList )
	{
		gi.printf( "No events.\n" );
		return;
	}

	if( dirtylist )
	{
		SortEventList();
	}

	l = 0;
	if( mask )
	{
		l = (int)strlen( mask );
	}

	hidden = 0;
	num = 0;
	n = sortedList->NumObjects();
	for( i = 1; i <= n; i++ )
	{
		eventnum = sortedList->ObjectAt( i );
		name = commandList->ObjectAt( eventnum )->c_str();
		flags = flagList->ObjectAt( eventnum );

		if( flags & EV_HIDE )
		{
			hidden++;
			continue;
		}

		if( mask && Q_strncasecmp( name.c_str(), mask, l ) )
		{
			continue;
		}

		num++;

		text = "   ";
		p = 0;
		if( flags & EV_CONSOLE )
		{
			text[ p++ ] = '*';
		}
		if( flags & EV_CHEAT )
		{
			text[ p++ ] = 'C';
		}

		gi.printf( "%4d : %s%s\n", eventnum, text.c_str(), name.c_str() );
	}

	gi.printf( "\n* = console command.\nC = cheat command.\n\n"
			   "Printed %d of %d total commands.\n", num, n - hidden );

	if( developer->value && hidden )
	{
		gi.printf( "Suppressed %d commands.\n", hidden );
	}
}

void Event::initCommandList( void )
{
	int flags;
	str* n;

	flags = 0;
	commandList = new Container<str*>;

	n = new str( "NULL" );
	NullEvent.eventnum = commandList->AddObject( n );

	flagList = new Container<int>;
	flagList->AddObject( flags );

	sortedList = new Container<int>;
	sortedList->AddObject( NullEvent.eventnum );

	dirtylist = false;

	NullEvent.data = NULL;
	NullEvent.info.inuse = 0;
	NullEvent.info.source = EV_FROM_CODE;
	NullEvent.info.flags = 0;
	NullEvent.info.linenumber = 0;
}

Event::Event()
{
	info.inuse = 0;
	info.source = EV_FROM_CODE;
	info.flags = 0;
	info.linenumber = 0;
	threadnum = -1;
	eventnum = 0;
	data = NULL;
	name = NULL;
}

Event::Event( int num )
{
	if( !commandList )
	{
		initCommandList();
	}

	assert( ( num > 0 ) && num <= commandList->NumObjects() );

	if( ( num <= 0 ) || ( num > commandList->NumObjects() ) )
	{
		num = 0;
		name = NULL;
		info.flags = 0;
	}
	else
	{
		name = commandList->ObjectAt( num )->c_str();
		info.flags = flagList->ObjectAt( num );
	}

	eventnum = num;
	data = NULL;
	info.inuse = 0;
	info.source = EV_FROM_CODE;
	info.linenumber = 0;
	threadnum = -1;
}

Event::Event( const Event& ev )
{
	int num;
	int i;

	eventnum = ( int )ev;
	assert( ( eventnum > 0 ) && eventnum <= commandList->NumObjects() );
	data = NULL;

	name = commandList->ObjectAt( eventnum )->c_str();
	info.inuse = 0;
	info.source = ev.info.source;
	info.flags = ev.info.flags;
	info.linenumber = ev.info.linenumber;
	threadnum = ev.threadnum;

	if( ev.data )
	{
		num = ev.data->NumObjects();

		data = new Container<str>;
		data->Resize( num );

		for( i = 1; i <= num; i++ )
		{
			data->AddObject( ev.data->ObjectAt( i ) );
		}
	}
}

Event::Event( Event* ev )
{
	int num;
	int i;

	assert( ev );
	if( !ev )
	{
		Class::error( "Event", "NULL Event\n" );
	}

	eventnum = ( int ) * ev;
	assert( ( eventnum > 0 ) && eventnum <= commandList->NumObjects() );
	data = NULL;
	name = commandList->ObjectAt( eventnum )->c_str();
	info.inuse = 0;
	info.source = ev->info.source;
	info.flags = ev->info.flags;
	info.linenumber = ev->info.linenumber;
	threadnum = ev->threadnum;
	if( ev->data )
	{
		num = ev->data->NumObjects();

		data = new Container<str>;
		data->Resize( num );

		for( i = 1; i <= num; i++ )
		{
			data->AddObject( ev->data->ObjectAt( i ) );
		}
	}
}

Event::Event( const char* command, int flags )
{
	if( !commandList )
	{
		initCommandList();
	}

	eventnum = FindEvent( command );
	if( !eventnum )
	{
		str* t = new str( command );
		eventnum = commandList->AddObject( t );
		// check for default flags
		if( flags == -1 )
		{
			flags = 0;
		}
		flagList->AddObject( ( int )flags );
		sortedList->AddObject( eventnum );
		dirtylist = true;
	}

	// Use the name stored in the command list in case the string passed in
	// is not in static memory.
	name = commandList->ObjectAt( eventnum )->c_str();

	data = NULL;
	info.inuse = 0;
	info.source = EV_FROM_CODE;
	info.linenumber = 0;
	threadnum = -1;

	// If flags have changed, let the user know.  It's probably a development bug.
	int& flagobj = flagList->ObjectAt( eventnum );

	// check for default flags
	if( flags == -1 )
	{
		flags = flagobj;
	}

	//assert(flags == flagobj); // THIS ALWAYS FIRES IN DEBUG BUILDS
	if( flags != flagobj )
	{
		// Flags not equal.  Use combined value.
		flagobj |= flags;
	}

	info.flags = flagobj;
}

Event::Event( str& command, int flags )
{
	str* t;

	if( !commandList )
	{
		initCommandList();
	}

	eventnum = FindEvent( command );
	if( !eventnum )
	{
		t = new str( command );
		eventnum = commandList->AddObject( t );
		// check for default flags
		if( flags == -1 )
		{
			flags = 0;
		}
		flagList->AddObject( flags );
		sortedList->AddObject( eventnum );
		dirtylist = true;
	}

	// Use the name stored in the command list since the string passed in
	// is not in static memory.
	name = commandList->ObjectAt( eventnum )->c_str();
	data = NULL;
	info.inuse = 0;
	info.source = EV_FROM_CODE;
	info.linenumber = 0;
	threadnum = -1;

	// If flags have changed, let the user know.  It's probably a development bug.
	int& flagobj = flagList->ObjectAt( eventnum );

	// check for default flags
	if( flags == -1 )
	{
		flags = flagobj;
	}

	assert( flags == flagobj );
	if( flags != flagobj )
	{
		// Flags not equal.  Use combined value.
		flagobj |= flags;
	}

	info.flags = flagobj;
}

Event::~Event()
{
	if( data )
	{
		delete data;
		data = NULL;
	}
}

void Event::SetThread( ScriptThread* thread )
{
	if( thread )
	{
		threadnum = thread->ThreadNum();
	}
	else
	{
		threadnum = -1;
	}
}

ScriptThread* Event::GetThread( void )
{
	if( threadnum != -1 )
	{
		return Director.GetThread( threadnum );
	}

	return NULL;
}

void Event::Error( const char* fmt, ... )
{
	va_list argptr;
	char text[ 1024 ];
	ScriptThread*   thread;
	const char*     filename;

	va_start( argptr, fmt );
	Q_vsnprintf( text, sizeof(text), fmt, argptr );
	va_end( argptr );
	text[sizeof(text)-1] = 0;

	switch( GetSource() )
	{
		default:
		case EV_FROM_CODE:
			gi.dprintf( "Game: '%s' : %s\n", getName(), text );
			break;

		case EV_FROM_SCRIPT:
			thread = GetThread();
			filename = "Dead script";
			if( thread )
			{
				filename = thread->Filename();
			}
			gi.dprintf( "%s(%d): '%s' :\n%s\n", filename, info.linenumber, getName(), text );
			break;

		case EV_FROM_CONSOLE:
			gi.cprintf( GetConsoleEdict(), PRINT_HIGH, "Console: '%s' : %s\n", getName(), text );
			break;
	}
}

void Event::AddEntity( Entity* ent )
{
	char text[ 128 ];

	//assert( ent );
	if( !ent )
	{
		sprintf( text, "*0" );
	}
	else
	{
		sprintf( text, "*%d", ent->entnum );
	}
	AddString( text );
}

qboolean Event::IsVectorAt( int pos )
{
	const char* text;
	ScriptVariable* var;

	if( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) )
	{
		Error( "Index %d out of range.", pos );
		return false;
	}

	text = data->ObjectAt( pos ).c_str();
	assert( text );

	var = Director.GetExistingVariable( text );
	if( var )
	{
		text = var->stringValue();
	}

	if( text[ 0 ] == '(' )
	{
		// probably a vector, so say that it is
		return true;
	}

	// not a vector
	return false;
}

qboolean Event::IsEntityAt( int pos )
{
	const char*     name;
	int t;
	ScriptVariable* var;

	if( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) )
	{
		Error( "Index %d out of range.", pos );
		return false;
	}

	name = data->ObjectAt( pos ).c_str();
	assert( name );

	var = Director.GetExistingVariable( name );
	if( var )
	{
		name = var->stringValue();
	}

	if( name[ 0 ] == '$' )
	{
		t = G_FindTarget( 0, &name[ 1 ] );
		if( !t )
		{
			Error( "Entity with targetname of '%s' not found", &name[ 1 ] );

			return false;
		}
	}
	else
	{
		if( name[ 0 ] != '*' )
		{
			Error( "Expecting a '*'-prefixed entity number but found '%s'.", name );

			return false;
		}

		if( !IsNumeric( &name[ 1 ] ) )
		{
			Error( "Expecting a numeric value but found '%s'.", &name[ 1 ] );

			return false;
		}
		else
		{
			t = atoi( &name[ 1 ] );
		}
	}

	if( ( t < 0 ) || ( t > game.maxentities ) )
	{
		Error( "%d out of valid range for entity.", t );
		return false;
	}

	// only return true if the entity exists
	return ( G_GetEntity( t ) != NULL );
}

qboolean Event::IsNumericAt( int pos )
{
	const char* text;
	ScriptVariable* var;

	if( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) )
	{
		Error( "Index %d out of range.", pos );
		return false;
	}

	text = data->ObjectAt( pos ).c_str();
	assert( text );

	var = Director.GetExistingVariable( text );
	if( var )
	{
		text = var->stringValue();
	}

	return IsNumeric( text );
}

const char* Event::GetString( int pos )
{
	const char* text;
	ScriptVariable* var;

	if( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) )
	{
		Error( "Index %d out of range.", pos );
		return "";
	}

	text = data->ObjectAt( pos ).c_str();
	assert( text );

	var = Director.GetExistingVariable( text );
	if( var )
	{
		return var->stringValue();
	}
	return text;
}

int Event::GetInteger( int pos )
{
	const char* text;
	ScriptVariable* var;

	if( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) )
	{
		Error( "Index %d out of range.", pos );
		return 0;
	}

	text = data->ObjectAt( pos ).c_str();
	assert( text );

	var = Director.GetExistingVariable( text );
	if( var )
	{
		if( !IsNumeric( var->stringValue() ) )
		{
			Error( "Variable %s contains non-numeric value '%s'", text, var->stringValue() );
		}
		return var->intValue();
	}

	if( !IsNumeric( text ) )
	{
		Error( "Expecting a numeric value but found '%s'.", text );
		return 0;
	}

	return atoi( text );
}

double Event::GetDouble( int pos )
{
	const char* text;
	ScriptVariable* var;

	if( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) )
	{
		Error( "Index %d out of range.", pos );
		return 0;
	}

	text = data->ObjectAt( pos ).c_str();
	assert( text );

	var = Director.GetExistingVariable( text );
	if( var )
	{
		if( !IsNumeric( var->stringValue() ) )
		{
			Error( "Variable %s contains non-numeric value '%s'", text, var->stringValue() );
		}
		return ( double )var->floatValue();
	}

	if( !IsNumeric( text ) )
	{
		Error( "Expecting a numeric value but found '%s'.", text );
		return 0;
	}

	return ( double )atof( text );
}

float Event::GetFloat( int pos )
{
	const char* text;
	ScriptVariable* var;

	if( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) )
	{
		Error( "Index %d out of range.", pos );
		return 0;
	}

	text = data->ObjectAt( pos ).c_str();
	assert( text );

	var = Director.GetExistingVariable( text );
	if( var )
	{
		if( !IsNumeric( var->stringValue() ) )
		{
			Error( "Variable %s contains non-numeric value '%s'", text, var->stringValue() );
		}
		return var->floatValue();
	}

	if( !IsNumeric( text ) )
	{
		Error( "Expecting a numeric value but found '%s'.", text );
		return 0;
	}

	return atof( text );
}

Vector Event::GetVector( int pos )
{
	const char* text;
	ScriptVariable* var;

	if( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) )
	{
		Error( "Index %d out of range.", pos );
		return vec_zero;
	}

	text = data->ObjectAt( pos ).c_str();
	assert( text );

	var = Director.GetExistingVariable( text );
	if( var )
	{
		text = var->stringValue();
	}

	// Check if this is a ()-based vector
	// we accept both, but using parenthesis we can determine if it is a vector or not
	if( text[ 0 ] == '(' )
	{
		return Vector( &text[ 1 ] );
	}

	// give an error, but try converting it anyways
	if( developer->value > 1 )
	{
		Error( "Vector '%s' does not include '(' ')'.", text );
	}
	return Vector( text );
}

Entity* Event::GetEntity( int pos )
{
	const char*     name;
	int t;
	ScriptVariable* var;

	if( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) )
	{
		Error( "Index %d out of range.", pos );
		return NULL;
	}

	name = data->ObjectAt( pos ).c_str();
	assert( name );

	var = Director.GetExistingVariable( name );
	if( var )
	{
		name = var->stringValue();
	}

	if( name[ 0 ] == '$' )
	{
		t = G_FindTarget( 0, &name[ 1 ] );
		if( !t )
		{
			Error( "Entity with targetname of '%s' not found", &name[ 1 ] );

			return NULL;
		}
	}
	else
	{
		if( name[ 0 ] != '*' )
		{
			Error( "Expecting a '*'-prefixed entity number but found '%s'.", name );

			return NULL;
		}

		if( !IsNumeric( &name[ 1 ] ) )
		{
			Error( "Expecting a numeric value but found '%s'.", &name[ 1 ] );

			return NULL;
		}
		else
		{
			t = atoi( &name[ 1 ] );
		}
	}

	if( ( t < 0 ) || ( t > game.maxentities ) )
	{
		Error( "%d out of valid range for entity.", t );
		return NULL;
	}

	return G_GetEntity( t );
}

ScriptVariable* Event::GetVariable( int pos )
{
	if( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) )
	{
		Error( "Index %d out of range.", pos );
		return NULL;
	}

	return Director.GetVariable( data->ObjectAt( pos ).c_str() );
}

void Event::Archive( Archiver& arc )
{
	str name;
	int num;
	int i;

	name = getName();
	arc.WriteString( name );

	arc.WriteRaw( &info, sizeof( info ) );
	arc.WriteInteger( threadnum );

	if( !data )
	{
		arc.WriteInteger( 0 );
	}
	else
	{
		num = data->NumObjects();

		arc.WriteInteger( num );
		for( i = 1; i <= num; i++ )
		{
			arc.WriteString( data->ObjectAt( i ) );
		}
	}
}

void Event::Unarchive( Archiver& arc )
{
	Event ev;
	str name;
	int i;
	int num;

	if( data )
	{
		delete data;
		data = NULL;
	}

	arc.ReadString( &name );
	*this = Event( name );

	arc.ReadRaw( &info, sizeof( info ) );
	arc.ReadInteger( &threadnum );

	arc.ReadInteger( &num );
	if( num )
	{
		data = new Container<str>;
		data->Resize( num );
		for( i = 1; i <= num; i++ )
		{
			arc.ReadString( &name );
			data->AddObject( name );
		}
	}
}

CLASS_DECLARATION( Class, Listener, NULL );

ResponseDef Listener::Responses[] =
{
	{ &EV_Remove, ( Response )& Listener::Remove },
	{ &EV_ScriptRemove, ( Response )& Listener::ScriptRemove },
	{ NULL, NULL }
};

void Listener::Remove( Event* e )
{
	this->CancelPendingEvents();
	delete this;
}

void Listener::ScriptRemove( Event* e )
{
	// Forces the remove to be done at a safe time
	PostEvent( EV_Remove, 0 );
}

#if 0
void Listener::FloatVar( Event& e, float* var, float defaultvalue )

{}

void Listener::IntVar( Event& e, int* var, float defaultvalue )

{}

void Listener::StringVar( Event& e, str* var, const char* defaultvalue )

{}

void Listener::StringVar( Event& e, char** var, const char* defaultvalue )

{}

void Listener::VectorVar( Event& e, Vector* var, Vector defaultvalue )

{}
#endif

qboolean Listener::ValidEvent( Event& e )
{
	ClassDef*   c;
	int ev;

	ev = ( int )e;

	c = this->classinfo();
	assert( ( ev >= 0 ) && ( ev < c->numEvents ) );
	if( ( ev < 0 ) || ( ev >= c->numEvents ) )
	{
		warning( "ValidEvent", "Event '%s' out of response range for class '%s'.  "
				 "Event probably invalid or allocated late.\n", e.getName(), getClassname() );
		return false;
	}

	return ( c->responseLookup[ ev ] != NULL );
}

qboolean Listener::ValidEvent( const char* name )
{
	ClassDef*   c;
	int ev;

	ev = Event::FindEvent( name );

	c = this->classinfo();
	assert( ( ev >= 0 ) && ( ev < c->numEvents ) );
	if( ( ev < 0 ) || ( ev >= c->numEvents ) )
	{
		warning( "ValidEvent", "Event '%s' out of response range for class '%s'.  "
				 "Event probably invalid or allocated late.\n", name, getClassname() );
		return false;
	}

	return ( c->responseLookup[ ev ] != NULL );
}

qboolean Listener::EventPending( Event& ev )
{
	eventcache_t* event;
	int eventnum;

	assert( EventQueue );
	assert( EventQueue->next );

	event = EventQueue->next;

	eventnum = ( int )ev;
	while( event != EventQueue )
	{
		if( ( event->obj == this ) && ( ( int )*event->event == eventnum ) )
		{
			return true;
		}
		event = event->next;
	}

	return false;
}

inline qboolean Listener::CheckEventFlags( Event* event )
{
	// Special handling of console events
	if( event->GetSource() == EV_FROM_CONSOLE )
	{
		if( !( event->info.flags & ( EV_CONSOLE | EV_CHEAT ) ) )
		{
			if( isSubclassOf( Entity ) )
			{
				Entity* ent;

				ent = ( Entity* )this;
				gi.cprintf( ent->edict, PRINT_HIGH, "Command not available from console.\n" );
			}

			// don't process
			return false;
		}

		// don't allow console cheats during deathmatch unless the server says it's ok.
		if( ( event->info.flags & EV_CHEAT ) && deathmatch->value && !sv_cheats->value )
		{
			if( isSubclassOf( Entity ) )
			{
				Entity* ent;

				ent = ( Entity* )this;
				gi.cprintf( ent->edict, PRINT_HIGH, "You must run the server with '+set cheats 1' to enable this command.\n" );
			}

			// don't process
			return false;
		}
	}

	// ok to process
	return true;
}

qboolean Listener::ProcessEvent( Event* event )
{
	ClassDef* c;
	int ev;
	int i;

	// Prevent overflow of the inuse count
	if( event->info.inuse >= MAX_EVENT_USE )
	{
		gi.error( "ProcessEvent : Event usage overflow on '%s' event.  Possible infinite loop.\n", event->getName() );
	}

	if( g_showevents->value )
	{
		int n;
		str text;

		text = va( "%.1f: %s", level.time, this->getClassname() );
		if( isSubclassOf( Entity ) )
		{
			text += va( " (*%d) ", ( ( Entity* )this )->entnum );
			if( ( ( Entity* )this )->Targeted() )
			{
				text += va( "'%s'", ( ( Entity* )this )->TargetName() );
			}
		}
		else if( isSubclassOf( ScriptThread ) )
		{
			text += va( " #%d:'%s'", ( ( ScriptThread* )this )->ThreadNum(), ( ( ScriptThread* )this )->ThreadName() );
		}
		else if( isSubclassOf( ScriptVariable ) )
		{
			text += va( " '%s'", ( ( ScriptVariable* )this )->getName() );
		}

		switch( event->GetSource() )
		{
			default:
			case EV_FROM_CODE:
				text += " : Code :";
				break;

			case EV_FROM_SCRIPT:
				assert( event->GetThread() );
				text += va( " : %s(%d) :", event->GetThread()->Filename(), event->info.linenumber );
				break;

			case EV_FROM_CONSOLE:
				text += " : Console :";
				break;
		}

		text += event->getName();
		n = event->NumArgs();
		for( i = 1; i <= n; i++ )
		{
			text += va( " %s", event->GetToken( i ) );
		}

		text += "\n";

		if( !g_watch->value || ( isSubclassOf( Entity ) && ( g_watch->value == ( ( Entity* )this )->entnum ) ) )
		{
			if( g_showevents->value == 2 )
			{
				G_DebugPrintf( text.c_str() );
			}
			else
			{
				gi.dprintf( "%s", text.c_str() );
			}
		}
	}

	ev = ( int ) * event;
	c = this->classinfo();
	if( ev >= c->numEvents )
	{
		event->Error( "Event out of response range for class '%s'.  Event probably invalid or allocated late.\n", getClassname() );
		return false;
	}

	if( c->responseLookup[ ev ] )
	{
		int start;
		int end;

		event->info.inuse++;

		if( !g_timeevents->value )
		{
			// only process the event if we allow it
			if( CheckEventFlags( event ) )
			{
				( this->**c->responseLookup[ ev ] )( event );
			}
		}
		else
		{
			start = G_Milliseconds();

			// only process the event if we allow it
			if( CheckEventFlags( event ) )
			{
				( this->**c->responseLookup[ ev ] )( event );
			}

			end = G_Milliseconds();
			if( g_timeevents->value == 1 )
			{
				gi.printf( "'%s' : %d\n", event->getName(), end - start );
			}
			else
			{
				G_DebugPrintf( "'%s' : %d\n", event->getName(), end - start );
			}
		}

		// Prevent an event from being freed twice, in case it's been re-used
		event->info.inuse--;
		if( !event->info.inuse )
		{
			delete event;
		}

		return true;
	}

	if( !event->info.inuse )
	{
		delete event;
	}

	return false;
}

void Listener::PostEvent( Event* ev, float time )
{
	eventcache_t* newevent;
	eventcache_t* event;

	if( LoadingSavegame )
	{
		if( !ev->info.inuse )
		{
			delete ev;
		}

		return;
	}

	if( LL_Empty( FreeEvents, next, prev ) )
	{
		gi.error( "PostEvent : No more free events on '%s' event.\n", ev->getName() );
		return;
	}

	newevent = FreeEvents->next;
	LL_Remove( newevent, next, prev );

	// Probably don't have this many events, but it's a good safety precaution
	if( ev->info.inuse >= MAX_EVENT_USE )
	{
		gi.error( "PostEvent : Event usage overflow on '%s' event.  Possible infinite loop.\n", ev->getName() );
		return;
	}

	ev->info.inuse++;

	newevent->obj       = this;
	newevent->event = ev;
	newevent->time      = level.time + time;

	event = EventQueue->next;
	while( ( event != EventQueue ) && ( newevent->time >= event->time ) )
	{
		event = event->next;
	}

	LL_Add( event, newevent, next, prev );
	numEvents++;
}

qboolean Listener::PostponeEvent( Event& ev, float time )
{
	eventcache_t* event;
	eventcache_t* node;
	int eventnum;

	assert( EventQueue );
	assert( EventQueue->next );

	eventnum = ( int )ev;

	event = EventQueue->next;
	while( event != EventQueue )
	{
		if( ( event->obj == this ) && ( ( int )*event->event == eventnum ) )
		{
			event->time += time;

			node = event->next;
			while( ( node != EventQueue ) && ( event->time >= node->time ) )
			{
				node = node->next;
			}

			LL_Remove( event, next, prev );
			LL_Add( node, event, next, prev );

			return true;
		}
		event = event->next;
	}

	return false;
}

void Listener::CancelEventsOfType( Event* ev )
{
	eventcache_t* event;
	eventcache_t* next;
	int eventnum;

	assert( EventQueue );
	assert( EventQueue->next );

	event = EventQueue->next;

	eventnum = ( int ) * ev;
	while( event != EventQueue )
	{
		next = event->next;
		if( ( event->obj == this ) && ( ( int )*event->event == eventnum ) )
		{
			delete event->event;
			event->event = NULL;
			LL_Remove( event, next, prev );
			LL_Add( FreeEvents, event, next, prev );
			numEvents--;
		}
		event = next;
	}
}

void Listener::CancelPendingEvents( void )
{
	eventcache_t* event;
	eventcache_t* next;

	assert( EventQueue );

	event = EventQueue->next;

	while( event && event != EventQueue )
	{
		next = event->next;
		if( event->obj == this )
		{
			delete event->event;
			event->event = NULL;
			LL_Remove( event, next, prev );
			LL_Add( FreeEvents, event, next, prev );
			numEvents--;
		}
		event = next;
	}
}

qboolean Listener::ProcessPendingEvents( void )
{
	eventcache_t* event;
	qboolean processedEvents;
	float t;

	assert( EventQueue );
	assert( EventQueue->next );
	assert( EventQueue->prev );

	processedEvents = false;

	t = level.time + 0.001;

	event = EventQueue->next;
	while( event != EventQueue )
	{
		assert( event );
		assert( event->event );
		assert( event->obj );

		if( event->time > t )
		{
			break;
		}

		if( event->obj != this )
		{
			// traverse normally
			event = event->next;
		}
		else
		{
			LL_Remove( event, next, prev );
			numEvents--;

			assert( event->obj );

			// ProcessEvent increments the inuse count, so decrement it since we've already incremented it in PostEvent
			event->event->info.inuse--;

			event->obj->ProcessEvent( event->event );

			event->event = NULL;
			LL_Add( FreeEvents, event, next, prev );

			// start over, since can't guarantee that we didn't process any previous or following events
			event = EventQueue->next;

			processedEvents = true;
		}
	}

	return processedEvents;
}

Listener::~Listener()
{
	CancelPendingEvents();
}

void G_ClearEventList( void )
{
	int i;
	eventcache_t* e;

	LL_Reset( FreeEvents, next, prev );
	LL_Reset( EventQueue, next, prev );
	memset( Events, 0, sizeof( Events ) );

	for( e = &Events[ 0 ], i = 0; i < MAX_EVENTS; i++, e++ )
	{
		LL_Add( FreeEvents, e, next, prev );
	}

	numEvents = 0;
}

void G_ProcessPendingEvents( void )
{
	eventcache_t* event;
	float t;
	int num;
	int maxevents;

	assert( EventQueue );
	assert( EventQueue->next );
	assert( EventQueue->prev );

	maxevents = ( int )g_eventlimit->value;

	num = 0;
	t = level.time + 0.001;
	while( !LL_Empty( EventQueue, next, prev ) )
	{
		event = EventQueue->next;

		assert( event );
		assert( event->event );
		assert( event->obj );

		if( event->time > t )
		{
			break;
		}

		LL_Remove( event, next, prev );
		numEvents--;

		assert( event->obj );

		// ProcessEvent increments the inuse count, so decrement it since we've already incremented it in PostEvent
		assert( event->event->info.inuse > 0 );
		event->event->info.inuse--;

		event->obj->ProcessEvent( event->event );

		event->event = NULL;
		LL_Add( FreeEvents, event, next, prev );

		// Don't allow ourselves to stay in here too long.  An abnormally high number
		// of events being processed is evidence of an infinite loop of events.
		num++;
		if( num > maxevents )
		{
			gi.printf( "Event overflow.  Possible infinite loop in script.  "
					   "Enable g_showevents to trace.  Aborting frame.\n" );
			break;
		}
	}
}

void G_ArchiveEvents( Archiver& arc )
{
	eventcache_t* event;
	int num;

	assert( EventQueue );
	assert( EventQueue->next );
	assert( EventQueue->prev );

	num = 0;
	for( event = EventQueue->next; event != EventQueue; event = event->next )
	{
		assert( event );
		assert( event->event );
		assert( event->obj );

		if( event->obj->isSubclassOf( Entity ) &&
				( ( ( Entity* )event->obj )->flags & FL_DONTSAVE ) )
		{
			continue;
		}

		num++;
	}

	arc.WriteInteger( num );
	for( event = EventQueue->next; event != EventQueue; event = event->next )
	{
		assert( event );
		assert( event->event );
		assert( event->obj );

		if( event->obj->isSubclassOf( Entity ) &&
				( ( ( Entity* )event->obj )->flags & FL_DONTSAVE ) )
		{
			continue;
		}

		arc.WriteObjectPointer( event->obj );
		arc.WriteEvent( event->event );
		arc.WriteFloat( event->time );
	}
}

void G_UnarchiveEvents( Archiver& arc )
{
	eventcache_t* e;
	int i;

	LL_Reset( FreeEvents, next, prev );
	LL_Reset( EventQueue, next, prev );
	memset( Events, 0, sizeof( Events ) );

	arc.ReadInteger( &numEvents );
	for( e = &Events[ 0 ], i = 0; i < numEvents; i++, e++ )
	{
		arc.ReadObjectPointer( ( Class** )&e->obj );
		e->event = new Event();
		arc.ReadEvent( e->event );
		arc.ReadFloat( &e->time );

		LL_Add( EventQueue, e, next, prev );
	}

	for( ; i < MAX_EVENTS; i++, e++ )
	{
		LL_Add( FreeEvents, e, next, prev );
	}
}

void G_InitEvents( void )
{
	g_numevents = gi.cvar( "g_numevents", "0", 0 );
	g_showevents = gi.cvar( "g_showevents", "0", 0 );
	g_eventlimit = gi.cvar( "g_eventlimit", "1500", 0 );
	g_timeevents = gi.cvar( "g_timeevents", "0", 0 );
	g_watch = gi.cvar( "g_watch", "0", 0 );

	BuildEventResponses();
	G_ClearEventList();

	// Sort the list before we go on since we won't be adding any more events
	Event::SortEventList();
}
