// Copyright (C) 1997 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.
//
// DESCRIPTION:
// Script masters are invisible entities that are spawned at the begining of each
// map.  They simple parse the script and send commands to the specified objects
// at the apropriate time.  Using a combination of simple commands, very complex
// scripted events can occur.
//

#include "g_local.h"
#include "class.h"
#include "scriptmaster.h"
#include "container.h"
#include "scriptvariable.h"
#include "surface.h"
#include "console.h"
#include "misc.h"
#include "specialfx.h"
#include "worldspawn.h"
#include "player.h"
#ifdef SIN_ARCADE
#include "arcade_comm.h"
#endif

ScriptVariableList gameVars;
ScriptVariableList levelVars;
ScriptVariableList parmVars;
ScriptVariableList consoleVars;
static ScriptVariablePtr GameTime = NULL;

Event EV_ProcessCommands( "processCommands" );
Event EV_Script_NewOrders( "newOrders" );
Event EV_ScriptThread_Execute( "execute" );
Event EV_ScriptThread_Callback( "script_callback" );
Event EV_ScriptThread_ThreadCallback( "thread_callback" );
Event EV_ScriptThread_ConsoleCallback( "console_callback" );
Event EV_ScriptThread_VariableCallback( "variable_callback" );
Event EV_ScriptThread_DeathCallback( "death_callback" );
Event EV_ScriptThread_CreateThread( "thread" );
Event EV_ScriptThread_TerminateThread( "terminate" );
Event EV_ScriptThread_ControlObject( "control" );
Event EV_ScriptThread_Goto( "goto" );
Event EV_ScriptThread_Pause( "pause" );
Event EV_ScriptThread_Wait( "wait" );
Event EV_ScriptThread_WaitFor( "waitFor" );
Event EV_ScriptThread_WaitForThread( "waitForThread" );
Event EV_ScriptThread_WaitForConsole( "waitForConsole" );
Event EV_ScriptThread_WaitForVariable( "waitForVariable" );
Event EV_ScriptThread_WaitForDeath( "waitForDeath" );
Event EV_ScriptThread_WaitForSound( "waitForSound" );
Event EV_ScriptThread_WaitForPlayer( "waitForPlayer" );
Event EV_ScriptThread_End( "end" );
Event EV_ScriptThread_Print( "print" );
Event EV_ScriptThread_PrintInt( "printint" );
Event EV_ScriptThread_PrintFloat( "printfloat" );
Event EV_ScriptThread_PrintVector( "printvector" );
Event EV_ScriptThread_NewLine( "newline" );
Event EV_ScriptThread_UserPrint( "uprint" );
Event EV_ScriptThread_UserPrintInt( "uprintint" );
Event EV_ScriptThread_UserPrintFloat( "uprintfloat" );
Event EV_ScriptThread_UserPrintVector( "uprintvector" );
Event EV_ScriptThread_UserNewLine( "unewline" );
Event EV_ScriptThread_Assert( "assert" );
Event EV_ScriptThread_Break( "break" );
Event EV_ScriptThread_Clear( "clear" );
Event EV_ScriptThread_Trigger( "trigger" );
Event EV_ScriptThread_Spawn( "spawn", EV_CHEAT );
Event EV_ScriptThread_Map( "map" );
Event EV_ScriptThread_SetCvar( "setcvar" );
Event EV_ScriptThread_CueCamera( "cuecamera" );
Event EV_ScriptThread_CuePlayer( "cueplayer" );
Event EV_ScriptThread_FreezePlayer( "freezeplayer" );
Event EV_ScriptThread_ReleasePlayer( "releaseplayer" );
Event EV_ScriptThread_Menu( "menu" );
Event EV_ScriptThread_MissionFailed( "missionfailed" );
Event EV_ScriptThread_KillEnt( "killent", EV_CONSOLE );
Event EV_ScriptThread_KillClass( "killclass", EV_CONSOLE );
Event EV_ScriptThread_RemoveEnt( "removeent", EV_CONSOLE );
Event EV_ScriptThread_RemoveClass( "removeclass", EV_CONSOLE );
Event EV_ScriptThread_TriggerFromEnt( "triggerfroment" ); //###

// client/server flow control
Event EV_ScriptThread_ServerOnly( "server" );
Event EV_ScriptThread_ClientOnly( "client" );
Event EV_ScriptThread_StuffCommand( "stuffcmd" );
Event EV_ScriptThread_Training( "training" );
Event EV_ScriptThread_ClearSaveGames( "clearsavegames" );

//
// world stuff
//
Event EV_ScriptThread_SetLightStyle( "lightstyle" );
Event EV_ScriptThread_RegisterAlias( "alias" );
Event EV_ScriptThread_RegisterAliasAndCache( "aliascache" );
Event EV_ScriptThread_SetCinematic( "cinematic" );
Event EV_ScriptThread_SetNonCinematic( "noncinematic" );
Event EV_ScriptThread_SetSkipThread( "skipthread" );
Event EV_ScriptThread_AirClamp( "airclamp" );
// Precache specific
Event EV_ScriptThread_Precache_Model( "cachemodel" );
Event EV_ScriptThread_Precache_PlayerModel( "cacheplayermodel" );
Event EV_ScriptThread_Precache_BikeModel( "cachebikemodel" ); //###
Event EV_ScriptThread_Precache_Sound( "cachesound" );
// set dialog script
Event EV_ScriptThread_SetDialogScript( "setdialogscript" );
// fades for movies
Event EV_ScriptThread_FadeIn( "fadein" );
Event EV_ScriptThread_FadeOut( "fadeout" );
Event EV_ScriptThread_Hud( "hud" );
Event EV_ScriptThread_LoadOverlay( "loadoverlay" );
Event EV_ScriptThread_LoadIntermission( "loadintermission" );
Event EV_ScriptThread_IntermissionLayout( "intermissionlayout" );
Event EV_ScriptThread_Overlay( "overlay" );
Event EV_ScriptThread_ScreenPrint( "screenprint" );
Event EV_ScriptThread_ScreenPrintFile( "screenprintfile" );
Event EV_ScriptThread_ClearScreenPrintFile( "clearscreenprintfile" );
Event EV_ScriptThread_MapName( "mapname" );
Event EV_ScriptThread_EndGame( "endgame" );

// music command
Event EV_ScriptThread_MusicEvent( "music" );
Event EV_ScriptThread_ForceMusicEvent( "forcemusic" );
Event EV_ScriptThread_SoundtrackEvent( "soundtrack" );

// Precache specific
Event EV_ScriptThread_JC_Hearable( "jc_hearable" );
Event EV_ScriptThread_JC_Not_Hearable( "jc_not_hearable" );

// crucial dialog command
Event EV_ScriptThread_CrucialDialog( "crucialdialog" );

// dialog sound command
Event EV_ScriptThread_DialogSound( "dialogsound" );

CLASS_DECLARATION( Class, ThreadMarker, NULL );

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

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

ScriptMaster Director;

CLASS_DECLARATION( Listener, ScriptMaster, NULL );

ScriptMaster::ScriptMaster()
{
	threadIndex     = 0;
	currentThread   = NULL;
	player_ready = false;
}

ScriptMaster::~ScriptMaster()
{
	GameTime = NULL;
	CloseScript();
}

EXPORT_FROM_DLL void ScriptMaster::CloseScript( void )
{
	KillThreads();
	ScriptLib.CloseScripts();
	GameTime = NULL;
}

EXPORT_FROM_DLL void ScriptMaster::KillThreads( void )
{
	int i;
	int num;

	num = Threads.NumObjects();

	// Clear the waitfor's from each thread before deleting
	// so they don't get triggered.
	for( i = num; i > 0; i-- )
	{
		Threads.ObjectAt( i )->ClearWaitFor();
	}

	for( i = num; i > 0; i-- )
	{
		delete Threads.ObjectAt( i );
	}

	Threads.FreeObjectList();

	threadIndex = 0;
	currentThread = NULL;
}

EXPORT_FROM_DLL qboolean ScriptMaster::NotifyOtherThreads( int num )
{
	ScriptThread *thread1;
	ScriptThread *thread2;
	int i;
	int n;
	Event *ev;

	thread1 = GetThread( num );
	assert( thread1 );
	n = Threads.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		thread2 = Threads.ObjectAt( i );
		assert( thread2 );
		if ( thread2->WaitingOnThread() == thread1 )
		{
			ev = new Event( EV_ScriptThread_ThreadCallback );
			ev->SetThread( thread1 );
			thread2->ProcessEvent( ev );

			return true;
		}
	}
	return false;
}


EXPORT_FROM_DLL void ScriptMaster::DeathMessage( const char *name )
{
	ScriptThread         *thread;
	Event                *ev;
	int i,n;

	// Look for threads that are waiting for this name
	n = Threads.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		const char *waiting;
		thread = Threads.ObjectAt( i );
		assert( thread );
		if (thread)
		{
			waiting = thread->WaitingOnDeath();
			if (waiting)
			{
				if (!strcmp( waiting, name ))
				{
					ev = new Event( EV_ScriptThread_DeathCallback );
					thread->ProcessEvent( ev );
				}
			}
		}
	}
}

EXPORT_FROM_DLL qboolean ScriptMaster::PlayerReady( void )
{
	return player_ready;
}

EXPORT_FROM_DLL void ScriptMaster::PlayerNotReady( void )
{
	player_ready = false;
}

EXPORT_FROM_DLL void ScriptMaster::PlayerSpawned( void )
{
	ScriptThread         *thread;
	int i,n;

	player_ready = true;
	// Look for threads that are waiting for the player
	n = Threads.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		thread = Threads.ObjectAt( i );
		assert( thread );
		if (thread)
		{
			if ( thread->WaitingOnPlayer() )
			{
				thread->ClearWaitFor();
				thread->Start( -1 );
			}
		}
	}
}

EXPORT_FROM_DLL void ScriptMaster::CreateConsoleUser( const char *console_name,int user )
{
	ScriptVariable         *var;
	ScriptVariableList   *vars;
	const char               *v;
	char varname[256];

	// Create a variable that holds the value for the slider.
	sprintf( varname,"console.%s",console_name );

	var = GetExistingVariable( varname );
	if ( !var )
	{
		vars = GetVarGroup( varname );
		if ( !vars )
		{
			return;
		}
		v = strchr( varname, '.' );
		assert( v );
		var = vars->CreateVariable( v + 1, user );
	}
	else
	{
		var->setIntValue( user );
	}
}



EXPORT_FROM_DLL void ScriptMaster::ConsoleVariable( const char *name,const char *text )
{
	ScriptThread         *thread;
	ScriptVariable         *var;
	ScriptVariableList   *vars;
	Event                *ev;
	int i,n;
	const char               *v;
	char varname[256];

	// Create a variable that holds the value for the slider.
	sprintf( varname,"console.%s",name );

	var = GetExistingVariable( varname );
	if ( !var )
	{
		vars = GetVarGroup( varname );
		if ( !vars )
		{
			return;
		}
		v = strchr( varname, '.' );
		assert( v );
		var = vars->CreateVariable( v + 1, text );
	}
	else
	{
		var->setStringValue( text );
	}

	// Look for threads that are waiting for this variable

	n = Threads.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		const char *waiting;

		thread = Threads.ObjectAt( i );
		assert( thread );
		if (thread)
		{
			waiting = thread->WaitingOnVariable();
			if (waiting)
			{
				if (!strcmp( waiting, name ))
				{
					ev = new Event( EV_ScriptThread_VariableCallback );
					thread->ProcessEvent( ev );
				}
			}
		}
	}
}

EXPORT_FROM_DLL void ScriptMaster::ConsoleInput( const char *name,const char *text )
{
	ScriptThread         *thread;
	ScriptVariable         *var;
	ScriptVariableList   *vars;
	Event                *ev;
	int i,n;
	const char               *v;
	char varname[256];

	// Create a variable that holds the text.
	sprintf( varname,"console.%s",name );

	var = GetExistingVariable( varname );
	if ( !var )
	{
		vars = GetVarGroup( varname );
		if ( !vars )
		{
			return;
		}
		v = strchr( varname, '.' );
		assert( v );
		var = vars->CreateVariable( v + 1, text );
	}
	else
	{
		var->setStringValue( text );
	}

	// Look for threads that are waiting for input from
	// this console.

	n = Threads.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		const char *waiting;
		thread = Threads.ObjectAt( i );
		assert( thread );
		if ( thread )
		{
			waiting = thread->WaitingOnConsole();
			if (waiting)
			{
				if (!strcmp( waiting, name ))
				{
					ev = new Event( EV_ScriptThread_ConsoleCallback );
					thread->ProcessEvent( ev );
				}
			}
		}
	}
}

EXPORT_FROM_DLL qboolean ScriptMaster::KillThread( int num )
{
	ScriptThread *thread;

	// Must be safely reentryable so that the thread destructor can tell us that it's being deleted.
	thread = GetThread( num );
	if ( thread )
	{
		if ( currentThread == thread )
		{
			SetCurrentThread( NULL );
		}
		delete thread;

		return true;
	}

	return false;
}

EXPORT_FROM_DLL qboolean ScriptMaster::RemoveThread( int num )
{
	ScriptThread *thread;
	int i;
	int n;

	// Must be safely reentryable so that the thread destructor can tell us that it's being deleted.
	n = Threads.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		thread = Threads.ObjectAt( i );
		assert( thread );
		if ( thread )
		{
			if ( thread->ThreadNum() == num )
			{
				Threads.RemoveObjectAt( i );
				if ( currentThread == thread )
				{
					SetCurrentThread( NULL );
				}
				return true;
			}
		}
	}

	return false;
}

EXPORT_FROM_DLL ScriptThread *ScriptMaster::CurrentThread( void )
{
	return currentThread;
}

EXPORT_FROM_DLL void ScriptMaster::SetCurrentThread( ScriptThread *thread )
{
	currentThread = thread;
}

EXPORT_FROM_DLL ScriptThread *ScriptMaster::CreateThread( GameScript *scr,const char *label,scripttype_t type )
{
	ScriptThread *thread;
	int threadnum;

	thread = new ScriptThread;

	thread->SetType( type );
	threadnum = GetUniqueThreadNumber();
	Threads.AddObject( thread );

	if ( !thread->Setup( threadnum, scr, label ) )
	{
		KillThread( threadnum );
		return NULL;
	}

	return thread;
}

//FIXME
// Why can't these be one function?
EXPORT_FROM_DLL ScriptThread *ScriptMaster::CreateThread( const char *name,scripttype_t type,const char *label )
{
	GameScript *scr;
	ScriptThread *thread;

	scr = ScriptLib.GetScript( name );
	if ( scr )
	{
		thread = CreateThread( scr, label, type );
		return thread;
	}

	return NULL;
}

EXPORT_FROM_DLL ScriptThread *ScriptMaster::GetThread( int num )
{
	int i;
	int n;
	ScriptThread *thread;

	n = Threads.NumObjects();
	for( i = 1; i <= n; i++ )
	{
		thread = Threads.ObjectAt( i );
		assert( thread );
		if ( thread )
		{
			if ( thread->ThreadNum() == num )
			{
				return thread;
			}
		}
	}

	return NULL;
}

EXPORT_FROM_DLL int ScriptMaster::GetUniqueThreadNumber( void )
{
	do
	{
		threadIndex++;
		if ( threadIndex == 0 )
		{
			threadIndex = 1;
		}
	}
	while( GetThread( threadIndex ) );

	return threadIndex;
}

EXPORT_FROM_DLL qboolean ScriptMaster::labelExists( GameScript * scr,const char *name )
{
	return scr->labelExists( name );
}

EXPORT_FROM_DLL qboolean ScriptMaster::Goto( GameScript * scr,const char *name )
{
	return scr->Goto( name );
}

EXPORT_FROM_DLL ScriptVariableList *ScriptMaster::GetVarGroup( const char *name )
{
	ScriptVariableList *vars;
	const char *v;
	char vargroup[ 20 ];
	int len;

	v = strchr( name, '.' );
	if ( !v )
	{
		return NULL;
	}

	len = v - name;
	if ( len > sizeof( vargroup ) - 1 )
	{
		len = sizeof( vargroup ) - 1;
	}
	memset( vargroup, 0, sizeof( vargroup ) );
	strncpy( vargroup, name, len );

	vars = NULL;
	if ( !strcmp( vargroup, "game" ) )
	{
		vars = &gameVars;
	}
	else if ( !strcmp( vargroup, "level" ) )
	{
		vars = &levelVars;
	}
	else if ( !strcmp( vargroup, "local" ) && currentThread )
	{
		vars = currentThread->Vars();
	}
	else if ( !strcmp( vargroup, "parm" ) )
	{
		vars = &parmVars;
	}
	else if ( !strcmp( vargroup, "console" ) )
	{
		vars = &consoleVars;
	}

	return vars;
}

EXPORT_FROM_DLL ScriptVariable *ScriptMaster::GetExistingVariable( const char *name )
{
	ScriptVariableList  *vars;
	const char              *v;

	vars = GetVarGroup( name );
	if ( !vars )
	{
		return NULL;
	}

	v = strchr( name, '.' );
	if ( !v )
	{
		return NULL;
	}

	return vars->GetVariable( v + 1 );
}

EXPORT_FROM_DLL ScriptVariable *ScriptMaster::GetVariable( const char *name )
{
	ScriptVariable          *var;
	ScriptVariableList  *vars;
	const char *v;

	var = GetExistingVariable( name );
	if ( !var )
	{
		vars = GetVarGroup( name );
		if ( !vars )
		{
			return NULL;
		}

		v = strchr( name, '.' );
		assert( v );
		var = vars->CreateVariable( v + 1, "" );
	}

	return var;
}

CLASS_DECLARATION( Listener, ScriptThread, NULL );

ResponseDef ScriptThread::Responses[] =
{
	{ &EV_ScriptThread_Execute,          ( Response )ScriptThread::Execute },
	{ &EV_MoveDone,                          ( Response )ScriptThread::ObjectMoveDone },
	{ &EV_ScriptThread_Callback,         ( Response )ScriptThread::ScriptCallback },
	{ &EV_ScriptThread_ThreadCallback,  ( Response )ScriptThread::ThreadCallback },
	{ &EV_ScriptThread_ConsoleCallback, ( Response )ScriptThread::ConsoleCallback },
	{ &EV_ScriptThread_VariableCallback,( Response )ScriptThread::VariableCallback },
	{ &EV_ScriptThread_DeathCallback,   ( Response )ScriptThread::DeathCallback },
	{ &EV_ScriptThread_CreateThread,     ( Response )ScriptThread::CreateThread },
	{ &EV_ScriptThread_TerminateThread, ( Response )ScriptThread::TerminateThread },
	{ &EV_ScriptThread_ControlObject,    ( Response )ScriptThread::ControlObject },
	{ &EV_ScriptThread_Goto,             ( Response )ScriptThread::EventGoto },
	{ &EV_ScriptThread_Pause,                ( Response )ScriptThread::EventPause },
	{ &EV_ScriptThread_Wait,             ( Response )ScriptThread::EventWait },
	{ &EV_ScriptThread_WaitFor,          ( Response )ScriptThread::EventWaitFor },
	{ &EV_ScriptThread_WaitForThread,    ( Response )ScriptThread::EventWaitForThread },
	{ &EV_ScriptThread_WaitForConsole,    ( Response )ScriptThread::EventWaitForConsole },
	{ &EV_ScriptThread_WaitForVariable,   ( Response )ScriptThread::EventWaitForVariable },
	{ &EV_ScriptThread_WaitForDeath,  ( Response )ScriptThread::EventWaitForDeath },
	{ &EV_ScriptThread_WaitForSound,  ( Response )ScriptThread::EventWaitForSound },
	{ &EV_ScriptThread_WaitForPlayer,     ( Response )ScriptThread::EventWaitForPlayer },
	{ &EV_ScriptThread_End,                  ( Response )ScriptThread::EventEnd },
	{ &EV_ScriptThread_Print,                ( Response )ScriptThread::Print },
	{ &EV_ScriptThread_PrintInt,         ( Response )ScriptThread::PrintInt },
	{ &EV_ScriptThread_PrintFloat,       ( Response )ScriptThread::PrintFloat },
	{ &EV_ScriptThread_PrintVector,      ( Response )ScriptThread::PrintVector },
	{ &EV_ScriptThread_NewLine,          ( Response )ScriptThread::NewLine },
	{ &EV_ScriptThread_UserPrint,            ( Response )ScriptThread::UserPrint },
	{ &EV_ScriptThread_UserPrintInt,     ( Response )ScriptThread::UserPrintInt },
	{ &EV_ScriptThread_UserPrintFloat,   ( Response )ScriptThread::UserPrintFloat },
	{ &EV_ScriptThread_UserPrintVector,  ( Response )ScriptThread::UserPrintVector },
	{ &EV_ScriptThread_UserNewLine,      ( Response )ScriptThread::UserNewLine },
	{ &EV_ScriptThread_Assert,              ( Response )ScriptThread::Assert },
	{ &EV_ScriptThread_Break,               ( Response )ScriptThread::Break },
	{ &EV_ScriptThread_Clear,                ( Response )ScriptThread::Clear },
	{ &EV_ScriptThread_Trigger,         ( Response )ScriptThread::TriggerEvent },
	{ &EV_ScriptThread_ServerOnly,      ( Response )ScriptThread::ServerEvent },
	{ &EV_ScriptThread_ClientOnly,      ( Response )ScriptThread::ClientEvent },
	{ &EV_ScriptThread_StuffCommand,        ( Response )ScriptThread::StuffCommand },
	{ &EV_ScriptThread_Training,           ( Response )ScriptThread::Training },
	{ &EV_ScriptThread_ClearSaveGames,  ( Response )ScriptThread::ClearSaveGames },
	{ &EV_ScriptThread_Spawn,             ( Response )ScriptThread::Spawn },
	{ &EV_ScriptThread_SetLightStyle,    ( Response )ScriptThread::EventSetLightStyle },
	{ &EV_ScriptThread_Precache_Model,   ( Response )ScriptThread::CacheModel },
	{ &EV_ScriptThread_Precache_PlayerModel,  ( Response )ScriptThread::CachePlayerModel },
	{ &EV_ScriptThread_Precache_BikeModel,    ( Response )ScriptThread::CacheBikeModel },   //###
	{ &EV_ScriptThread_Precache_Sound,   ( Response )ScriptThread::CacheSound },
	{ &EV_ScriptThread_RegisterAlias,    ( Response )ScriptThread::RegisterAlias },
	{ &EV_ScriptThread_RegisterAliasAndCache,    ( Response )ScriptThread::RegisterAliasAndCache },
	{ &EV_ScriptThread_Map,                 ( Response )ScriptThread::MapEvent },
	{ &EV_ScriptThread_SetCvar,         ( Response )ScriptThread::SetCvarEvent },
	{ &EV_ScriptThread_CueCamera,           ( Response )ScriptThread::CueCamera },
	{ &EV_ScriptThread_CuePlayer,           ( Response )ScriptThread::CuePlayer },
	{ &EV_ScriptThread_FreezePlayer,        ( Response )ScriptThread::FreezePlayer },
	{ &EV_ScriptThread_ReleasePlayer,   ( Response )ScriptThread::ReleasePlayer },
	{ &EV_DialogEvent,                 ( Response )ScriptThread::DialogEvent },
	{ &EV_ScriptThread_SetDialogScript, ( Response )ScriptThread::SetDialogScript },
	{ &EV_ScriptThread_FadeIn,        ( Response )ScriptThread::FadeIn },
	{ &EV_ScriptThread_FadeOut,           ( Response )ScriptThread::FadeOut },
	{ &EV_ScriptThread_Hud,           ( Response )ScriptThread::Hud },
	{ &EV_ScriptThread_Menu,              ( Response )ScriptThread::MenuEvent },
	{ &EV_ScriptThread_MusicEvent,        ( Response )ScriptThread::MusicEvent },
	{ &EV_ScriptThread_ForceMusicEvent, ( Response )ScriptThread::ForceMusicEvent },
	{ &EV_ScriptThread_SoundtrackEvent, ( Response )ScriptThread::SoundtrackEvent },
	{ &EV_ScriptThread_LoadOverlay,     ( Response )ScriptThread::LoadOverlay },
	{ &EV_ScriptThread_LoadIntermission,( Response )ScriptThread::LoadIntermission },
	{ &EV_ScriptThread_IntermissionLayout, ( Response )ScriptThread::IntermissionLayout },
	{ &EV_ScriptThread_Overlay,         ( Response )ScriptThread::Overlay },
	{ &EV_ScriptThread_ScreenPrint,     ( Response )ScriptThread::ScreenPrint },
	{ &EV_ScriptThread_ScreenPrintFile, ( Response )ScriptThread::ScreenPrintFile },
	{ &EV_ScriptThread_MapName,         ( Response )ScriptThread::MapName },
	{ &EV_ScriptThread_EndGame,         ( Response )ScriptThread::EndGame },
	{ &EV_ScriptThread_ClearScreenPrintFile, ( Response )ScriptThread::ClearScreenPrintFile },
	{ &EV_ScriptThread_SetCinematic,    ( Response )ScriptThread::SetCinematic },
	{ &EV_ScriptThread_SetNonCinematic, ( Response )ScriptThread::SetNonCinematic },
	{ &EV_ScriptThread_SetSkipThread,   ( Response )ScriptThread::SetSkipThread },
	{ &EV_ScriptThread_AirClamp,        ( Response )ScriptThread::AirClamp },
	{ &EV_ScriptThread_JC_Hearable,     ( Response )ScriptThread::JC_Hearable },
	{ &EV_ScriptThread_JC_Not_Hearable, ( Response )ScriptThread::JC_Not_Hearable },
	{ &EV_ScriptThread_MissionFailed,   ( Response )ScriptThread::MissionFailed },
	{ &EV_ScriptThread_KillEnt,         ( Response )ScriptThread::KillEnt },
	{ &EV_ScriptThread_RemoveEnt,           ( Response )ScriptThread::RemoveEnt },
	{ &EV_ScriptThread_KillClass,           ( Response )ScriptThread::KillClass },
	{ &EV_ScriptThread_RemoveClass,     ( Response )ScriptThread::RemoveClass },
	{ &EV_ScriptThread_CrucialDialog,   ( Response )ScriptThread::CrucialDialogEvent },
	{ &EV_ScriptThread_DialogSound,     ( Response )ScriptThread::DialogSoundEvent },
	{ &EV_AI_RecalcPaths,               ( Response )ScriptThread::PassToPathmanager },
	{ &EV_AI_CalcPath,                  ( Response )ScriptThread::PassToPathmanager },
	{ &EV_AI_DisconnectPath,            ( Response )ScriptThread::PassToPathmanager },
	{ &EV_ScriptThread_TriggerFromEnt, ( Response )ScriptThread::TriggerFromEntEvent },     //###
	{ NULL, NULL }
};

ScriptThread::ScriptThread()
{
	threadNum           = 0;
	ClearWaitFor();
	threadDying         = false;
	doneProcessing      = true;
	type                    = LEVEL_SCRIPT;
}

ScriptThread::~ScriptThread()
{
	Director.NotifyOtherThreads( threadNum );
	Director.RemoveThread( threadNum );
}

EXPORT_FROM_DLL void ScriptThread::ClearWaitFor( void )
{
	waitUntil = 0;
	waitingFor = "";
	waitingNumObjects = 0;
	waitingForThread = NULL;
	waitingForConsole = "";
	waitingForVariable = "";
	waitingForDeath = "";
	waitingForPlayer = false;
}

EXPORT_FROM_DLL void ScriptThread::SetType( scripttype_t newtype )
{
	type = newtype;
}

EXPORT_FROM_DLL scripttype_t ScriptThread::GetType( void )
{
	return type;
}

EXPORT_FROM_DLL int ScriptThread::ThreadNum( void )
{
	return threadNum;
}

EXPORT_FROM_DLL const char *ScriptThread::ThreadName( void )
{
	return threadName.c_str();
}

EXPORT_FROM_DLL int ScriptThread::CurrentLine( void )
{
	return linenumber;
}

EXPORT_FROM_DLL const char *ScriptThread::Filename( void )
{
	return script.Filename();
}

EXPORT_FROM_DLL ScriptThread *ScriptThread::WaitingOnThread( void )
{
	return waitingForThread;
}

EXPORT_FROM_DLL const char *ScriptThread::WaitingOnConsole( void )
{
	return waitingForConsole.c_str();
}

EXPORT_FROM_DLL const char *ScriptThread::WaitingOnVariable( void )
{
	return waitingForVariable.c_str();
}

EXPORT_FROM_DLL const char *ScriptThread::WaitingOnDeath( void )
{
	return waitingForDeath.c_str();
}

EXPORT_FROM_DLL qboolean ScriptThread::WaitingOnPlayer( void )
{
	return waitingForPlayer;
}

EXPORT_FROM_DLL ScriptVariableList *ScriptThread::Vars( void )
{
	return &localVars;
}


EXPORT_FROM_DLL qboolean ScriptThread::Setup( int num,GameScript *scr,const char *label )
{
	threadNum = num;

	ClearWaitFor();
	script.SetSourceScript( scr );
	if ( label && !Goto( label ) )
	{
		ScriptError( "Can't create thread.  Label '%s' not found", label );
		return false;
	}

	if ( label )
	{
		threadName = label;
	}
	else
	{
		threadName = script.Filename();
	}

	return true;
}

EXPORT_FROM_DLL qboolean ScriptThread::SetScript( const char *name )
{
	GameScript *scr;

	scr = ScriptLib.GetScript( name );
	if ( scr )
	{
		Setup( threadNum, scr, NULL );
		return true;
	}

	return false;
}

EXPORT_FROM_DLL qboolean ScriptThread::Goto( const char *name )
{
	qboolean result;

	result = ScriptLib.Goto( &script, name );
	if ( result )
	{
		// Cancel pending execute events when waitUntil is set
		if ( waitUntil )
		{
			CancelEventsOfType( EV_ScriptThread_Execute );
		}
		ClearWaitFor();
	}
	return result;
}

EXPORT_FROM_DLL qboolean ScriptThread::labelExists( const char *name )
{
	return ScriptLib.labelExists( &script, name );
}

EXPORT_FROM_DLL void ScriptThread::Mark( ThreadMarker *mark )
{
	assert( mark );

	mark->linenumber                = linenumber;
	mark->doneProcessing            = doneProcessing;
	mark->waitingFor                = waitingFor;
	mark->waitingForThread      = waitingForThread;
	mark->waitingForConsole     = waitingForConsole;
	mark->waitingForVariable    = waitingForVariable;
	mark->waitingForDeath       = waitingForDeath;
	mark->waitingForPlayer      = waitingForPlayer;
	mark->waitingNumObjects     = waitingNumObjects;
	if ( waitUntil )
	{
		// add one so that 0 is always reserved for no wait
		mark->waitUntil = waitUntil - level.time + 1;
	}
	else
	{
		mark->waitUntil = 0;
	}

	script.Mark( &mark->scriptmarker );
}

EXPORT_FROM_DLL void ScriptThread::Restore( ThreadMarker *mark )
{
	assert( mark );

	linenumber              = mark->linenumber;
	doneProcessing          = mark->doneProcessing;

	waitingFor              = mark->waitingFor;
	waitingForThread        = mark->waitingForThread;
	waitingForConsole       = mark->waitingForConsole;
	waitingForVariable  = mark->waitingForVariable;
	waitingForDeath     = mark->waitingForDeath;
	waitingForPlayer        = mark->waitingForPlayer;
	waitingNumObjects       = mark->waitingNumObjects;

	script.Restore( &mark->scriptmarker );

	if ( mark->waitUntil )
	{
		// subtract one since we added one when we stored it.
		// this way, 0 is always reserved for no wait
		// Cheezy, yeah, but since I'm commenting it, it's "ok". :)
		waitUntil = mark->waitUntil + level.time - 1;
		Start( waitUntil - level.time );
	}
}

EXPORT_FROM_DLL TargetList *ScriptThread::GetTargetList( str &targetname )
{
	TargetList *tlist;
	int num;
	int i;

	num = targets.NumObjects();
	for( i = 1; i <= num; i++ )
	{
		tlist = targets.ObjectAt( i );
		if ( targetname == tlist->targetname )
		{
			return tlist;
		}
	}

	tlist = world->GetTargetList( targetname );
	targets.AddObject( tlist );

	return tlist;
}

EXPORT_FROM_DLL void ScriptThread::SendCommandToSlaves( const char *name,Event *ev )
{
	Event          *sendevent;
	Entity     *ent;
	TargetList  *tlist;
	int i;
	int num;

	if ( name && name[ 0 ] )
	{
		tlist = GetTargetList( str( name+1 ) );
		num = tlist->list.NumObjects();
		for ( i = 1; i <= num; i++ )
		{
			ent = tlist->list.ObjectAt( i );

			assert( ent );

			sendevent = new Event( *ev );

			if ( !updateList.ObjectInList( ent->entnum ) )
			{
				updateList.AddObject( ent->entnum );

				// Tell the object that we're about to send it some orders
				ent->ProcessEvent( EV_Script_NewOrders );
			}

			// Send the command
			ent->ProcessEvent( sendevent );
		}
	}
	//
	// free up the event
	//
	delete ev;
}

qboolean ScriptThread::FindEvent( const char *name )
{
	if ( !Event::Exists( name ) )
	{
		ScriptError( "Unknown command '%s'\n", name );
		script.SkipToEOL();
		return false;
	}

	return true;
}

EXPORT_FROM_DLL void ScriptThread::ProcessCommand( int argc,const char **argv )
{
	ScriptVariableList *vars;
	ScriptVariable       *var;
	str command;
	str name;
	Event       *event;
	Entity  *ent;

	if ( argc < 1 )
	{
		return;
	}

	name = argv[ 0 ];
	if ( argc > 1 )
	{
		command = argv[ 1 ];
	}

	// Check for variable commands
	vars = Director.GetVarGroup( name.c_str() );
	if ( vars )
	{
		var = Director.GetVariable( name.c_str() );
		if ( var->isVariableCommand( command.c_str() ) )
		{
			event = new Event( command );
			event->SetSource( EV_FROM_SCRIPT );
			event->SetThread( this );
			event->SetLineNumber( linenumber );
			event->AddTokens( argc - 2, &argv[ 2 ] );
			var->ProcessEvent( event );
			return;
		}

		name = var->stringValue();
		if ( !name.length() )
		{
			ScriptError( "Uninitialized variable '%s' used for command '%s'", var->getName(), command.c_str() );
			return;
		}
		else if ( name[ 0 ] != '$' && name[ 0 ] != '@' && name[ 0 ] != '%' && name[ 0 ] != '*' )
		{
			ScriptError( "Invalid variable command '%s'", command.c_str() );
			return;
		}
	}

	// Check for object commands
	if ( name[ 0 ] == '$' )
	{
		if ( FindEvent( command.c_str() ) )
		{
			event = new Event( command );
			event->SetSource( EV_FROM_SCRIPT );
			event->SetThread( this );
			event->SetLineNumber( linenumber );
			event->AddTokens( argc - 2, &argv[ 2 ] );
			SendCommandToSlaves( name.c_str(), event );
		}
		return;
	}

	// Check for surface commands
	if ( name[ 0 ] == '@' )
	{
		if ( FindEvent( command.c_str() ) )
		{
			event = new Event( command );
			event->SetSource( EV_FROM_SCRIPT );
			event->SetThread( this );
			event->SetLineNumber( linenumber );
			event->AddToken( &name[ 1 ] );
			event->AddTokens( argc - 2, &argv[ 2 ] );
			surfaceManager.ProcessEvent( event );
		}
		return;
	}

	// Check for console commands
	if ( name[ 0 ] == '%' )
	{
		if ( FindEvent( command.c_str() ) )
		{
			event = new Event( command );
			event->SetSource( EV_FROM_SCRIPT );
			event->SetThread( this );
			event->SetLineNumber( linenumber );
			event->AddToken( &name[ 1 ] );
			event->AddTokens( argc - 2, &argv[ 2 ] );
			consoleManager.ProcessEvent( event );
		}
		return;
	}

	// Check for entnum commands
	if ( name[ 0 ] == '*' )
	{
		if ( !IsNumeric( &name[ 1 ] ) )
		{
			ScriptError( "Expecting numeric value for * command, but found '%s'\n", &name[ 1 ] );
		}
		else if ( FindEvent( command.c_str() ) )
		{
			ent = G_GetEntity( atoi( &name[ 1 ] ) );
			if ( ent )
			{
				event = new Event( command );
				event->SetSource( EV_FROM_SCRIPT );
				event->SetThread( this );
				event->SetLineNumber( linenumber );
				event->AddTokens( argc - 2, &argv[ 2 ] );
				ent->ProcessEvent( event );
			}
			else
			{
				ScriptError( "Entity not found for * command\n" );
			}
		}
		return;
	}

	// Handle global commands
	if ( FindEvent( name.c_str() ) )
	{
		event = new Event( name );
		event->SetSource( EV_FROM_SCRIPT );
		event->SetThread( this );
		event->SetLineNumber( linenumber );
		event->AddTokens( argc - 1, &argv[ 1 ] );
		if ( !ProcessEvent( event ) )
		{
			ScriptError( "Invalid global command '%s'\n", name.c_str() );
		}
	}
}

EXPORT_FROM_DLL void ScriptThread::ProcessCommandFromEvent( Event *ev,int startarg )
{
	int argc;
	int numargs;
	const char  *argv[ MAX_COMMANDS ];
	str args[ MAX_COMMANDS ];
	int i;

	numargs = ev->NumArgs();
	if ( numargs < startarg )
	{
		ev->Error( "Expecting statement after conditional", MAX_COMMANDS );
		return;
	}

	argc = numargs - startarg + 1;

	if ( argc >= MAX_COMMANDS )
	{
		ev->Error( "Line exceeds %d command limit", MAX_COMMANDS );
		return;
	}

	for( i = 0; i < argc; i++ )
	{
		args[ i ] = ev->GetToken( startarg + i );
		argv[ i ] = args[ i ].c_str();
	}

	ProcessCommand( argc, argv );
}

EXPORT_FROM_DLL void ScriptThread::Start( float delay )
{
	CancelEventsOfType( EV_ScriptThread_Execute );
	if ( delay < 0 )
		ProcessEvent( EV_ScriptThread_Execute );
	else
		PostEvent( EV_ScriptThread_Execute, delay );
}

EXPORT_FROM_DLL void ScriptThread::Execute( Event *ev )
{
	int num;
	ScriptThread *oldthread;
	int argc;
	const char *argv[ MAX_COMMANDS ];
	char args[ MAX_COMMANDS ][ MAXTOKEN ];
	ScriptVariable  *var;

	if ( threadDying )
	{
		return;
	}

	// set the current game time
	if ( !GameTime )
	{
		GameTime = gameVars.CreateVariable( "time", 0 );
	}

	GameTime->setFloatValue( level.time );

	// clear the updateList so that all objects moved this frame are notified before they receive any commands
	// we have to do this here as well as in DoMove, since DoMove may not be called
	updateList.ClearObjectList();

	oldthread = Director.CurrentThread();
	Director.SetCurrentThread( this );

	// if we're not being called from another thread, clear the parm variables
	if ( !oldthread )
	{
		parmVars.ClearList();
	}

	ClearWaitFor();

	var = Director.GetVariable( "parm.previousthread" );
	if ( oldthread )
	{
		var->setIntValue( oldthread->ThreadNum() );
	}
	else
	{
		var->setIntValue( 0 );
	}

	var = Director.GetVariable( "parm.currentthread" );

	doneProcessing = false;

	num = 0;
	while( ( num++ < 10000 ) && !doneProcessing && !threadDying )
	{
		// keep our thread number up to date
		var->setIntValue( threadNum );

		script.SkipNonToken( true );

		// save the line number for errors
		linenumber = script.GetLineNumber();

		argc = 0;
		while( script.TokenAvailable( false ) )
		{
			if ( argc >= MAX_COMMANDS )
			{
				ScriptError( "Line exceeds %d command limit", MAX_COMMANDS );
				script.SkipToEOL();
				break;
			}
			strcpy( args[ argc ], script.GetToken( false ) );
			argv[ argc ] = args[ argc ];
			argc++;
		}

		assert( argc > 0 );

		// Ignore labels
		if ( args[ 0 ][ strlen( args[ 0 ] ) - 1 ] != ':' )
		{
			ProcessCommand( argc, argv );
		}
	}

	if ( !doneProcessing )
	{
		gi.error( "Command overflow.  Possible infinite loop in thread '%s'.\n"
		          "Stopping on line %d of %s\n", threadName.c_str(), script.GetLineNumber(), script.Filename() );
	}

	Director.SetCurrentThread( oldthread );

	// Set the thread number on exit, in case we were called by someone who wants to know our thread
	var = Director.GetVariable( "parm.previousthread" );
	var->setIntValue( threadNum );
}

EXPORT_FROM_DLL void ScriptThread::ScriptError( const char *fmt,... )
{
	va_list argptr;
	char text[ 1024 ];

	va_start( argptr, fmt );
	vsprintf( text, fmt, argptr );
	va_end( argptr );

	gi.dprintf( "%s(%d):: %s\n", Filename(), linenumber, text );
}

//****************************************************************************************
//
// global commands
//
//****************************************************************************************

EXPORT_FROM_DLL qboolean ScriptThread::WaitingFor( Entity *obj )
{
	assert( obj );

	if ( waitingFor.length() )
	{
		if ( ( ( waitingFor[ 0 ] == '*' ) && ( atoi( &waitingFor.c_str()[ 1 ] ) == obj->entnum ) ) ||
		     ( waitingFor == obj->TargetName() ) )
		{
			return true;
		}
	}

	return false;
}

EXPORT_FROM_DLL void ScriptThread::ObjectMoveDone( Event *ev )
{
	Entity *obj;

	obj = ev->GetEntity( 1 );
	assert( obj );

#if 0
	gi.dprintf( "Move done %d:'%s'\n", obj->entnum, obj->TargetName() );
#endif

	if ( WaitingFor( obj ) )
	{
		waitingNumObjects--;
		if ( waitingNumObjects <= 0 )
		{
			ClearWaitFor();
			// start right away
			Start( -1 );
		}
	}
}

void ScriptThread::CreateThread( Event *ev )
{
	ScriptThread * pThread;

	pThread = Director.CreateThread( &script, ev->GetToken( 1 ) );
	if ( pThread )
	{
		// start right away
		pThread->Start( -1 );
	}
}

void ScriptThread::TerminateThread( Event *ev )
{
	int threadnum;
	ScriptThread *thread;

	threadnum = 0;
	// we specified the thread to kill
	if ( ev->NumArgs() > 0 )
	{
		threadnum = ev->GetInteger( 1 );
	}
	else
	{
		thread = ev->GetThread();
		if ( thread )
		{
			threadnum = thread->ThreadNum();
		}
	}

	if ( threadnum != 0 )
	{
		thread = Director.GetThread( threadnum );
		if ( thread && ( thread->GetType() == MODEL_SCRIPT ) && ( ev->GetSource() == EV_FROM_SCRIPT ) )
		{
			ev->Error( "Can't terminate an actor's thread via script." );
			return;
		}
		Director.KillThread( threadnum );
	}
}

void ScriptThread::ControlObject( Event *ev )
{
	ev->GetEntity( 1 );
}

void ScriptThread::EventGoto( Event *ev )
{
	const char *label;

	label = ev->GetToken( 1 );
	if ( !Goto( label ) )
	{
		ev->Error( "Label '%s' not found", label );
	}
}

void ScriptThread::EventPause( Event *ev )
{
	ClearWaitFor();
	doneProcessing = true;
}

void ScriptThread::EventWait( Event *ev )
{
	DoMove();

	ClearWaitFor();

	waitUntil = ev->GetFloat( 1 ) + level.time;

	Start( ev->GetFloat( 1 ) );
	doneProcessing = true;
}

void ScriptThread::EventWaitFor( Event *ev )
{
	Entity *ent;
	const char *objname;
	const char *tname;

	ClearWaitFor();
	doneProcessing = true;

	ent = ev->GetEntity( 1 );
	if ( ent )
	{
		objname = ev->GetString( 1 );
		tname = ent->TargetName();
		if ( objname && objname[ 0 ] == '*' )
		{
			if ( !IsNumeric( &objname[ 1 ] ) )
			{
				ScriptError( "Expecting *-prefixed numeric value for waitFor command, but found '%s'\n", objname );
				return;
			}

			waitingFor = objname;
		}
		else if ( !tname || !tname[ 0 ] )
		{
			// Probably won't happen, but check for it anyway.
			ScriptError( "Entity doesn't have a targetname.\n" );
			return;
		}
		else
		{
			TargetList  *tlist;
			waitingFor = tname;
			//
			// set the number of objects that belong to this targetname
			//
			tlist = GetTargetList( waitingFor );
			waitingNumObjects = tlist->list.NumObjects();
			if ( waitingNumObjects <= 0 )
			{
				waitingNumObjects = 1;
				ScriptError( "no objects of targetname %s found.\n", tname );
			}
			else
			{
				Entity * tent;
				int i;
				//
				// make sure all these objects are in the update list
				//
				for ( i = 1; i <= waitingNumObjects; i++ )
				{
					tent = tlist->list.ObjectAt( i );
					// add the object to the update list to make sure we tell it to do a move
					if ( !updateList.ObjectInList( tent->entnum ) )
					{
						updateList.AddObject( tent->entnum );
					}
				}
			}
		}

		// add the object to the update list to make sure we tell it to do a move
		if ( !updateList.ObjectInList( ent->entnum ) )
		{
			updateList.AddObject( ent->entnum );
		}
	}

	DoMove();
}

void ScriptThread::EventWaitForThread( Event *ev )
{
	doneProcessing = true;

	ClearWaitFor();
	waitingForThread = Director.GetThread( ev->GetInteger( 1 ) );
	if ( !waitingForThread )
	{
		ev->Error( "EventWaitForThread", "Thread %d not running", ev->GetInteger( 1 ) );
		return;
	}

	DoMove();
}

void ScriptThread::EventWaitForDeath( Event *ev )
{
	doneProcessing = true;

	ClearWaitFor();
	waitingForDeath = ev->GetString( 1 );
	if ( !waitingForDeath.length() )
	{
		ev->Error( "EventWaitForDeath", "Null name" );
		return;
	}

	DoMove();
}

void ScriptThread::EventWaitForConsole( Event *ev )
{
	doneProcessing = true;

	ClearWaitFor();
	waitingForConsole = ev->GetString( 1 );

	if ( !waitingForConsole.length() )
	{
		ev->Error( "EventWaitForConsole", "Null console" );
		return;
	}

	DoMove();
}

void ScriptThread::EventWaitForVariable( Event *ev )
{
	doneProcessing = true;

	ClearWaitFor();
	waitingForVariable = ev->GetString( 1 );

	if ( !waitingForVariable.length() )
	{
		ev->Error( "EventWaitForVariable", "Null variable" );
		return;
	}

	DoMove();
}

void ScriptThread::EventWaitForSound( Event *ev )
{
	str sound;
	float delay;

	ClearWaitFor();

	DoMove();

	delay = 0;
	sound = ev->GetString( 1 );
	if ( ev->NumArgs() > 1 )
		delay = ev->GetFloat( 2 );

	delay += gi.SoundLength( sound.c_str() );

	Start( delay );
	doneProcessing = true;
}

void ScriptThread::EventWaitForPlayer( Event *ev )
{
	if ( !Director.PlayerReady() )
	{
		doneProcessing = true;

		ClearWaitFor();
		waitingForPlayer = true;

		DoMove();
	}
}

void ScriptThread::EventEnd( Event *ev )
{
	ScriptVariable *var;
	const char *text;
	Entity *ent;

	ClearWaitFor();

	// If we're a model script, we have to tell our owning entity that we're done.
	if ( ( ev->GetSource() == EV_FROM_SCRIPT ) && ( type == MODEL_SCRIPT ) )
	{
		doneProcessing = true;
		var = Vars()->GetVariable( "self" );
		if ( !var )
		{
			ev->Error( "Model script ending without local.self set" );
		}
		else
		{
			text = var->stringValue();
			if ( ( text[ 0 ] == '*' ) && IsNumeric( &text[ 1 ] ) )
			{
				ent = G_GetEntity( atoi( &text[ 1 ] ) );

				// pass the event on to entity pointed to by self
				if ( ent )
				{
					ent->ProcessEvent( ev );
					return;
				}
			}
			else
			{
				ev->Error( "Model script ending.  Invalid value in local.self '%s'", text );
			}
		}
	}

	Director.NotifyOtherThreads( threadNum );
	PostEvent( EV_Remove, 0 );
	doneProcessing = true;
	threadDying = true;
}

void ScriptThread::Print( Event *ev )
{
	gi.dprintf( "%s", ev->GetString( 1 ) );
}

void ScriptThread::PrintInt( Event *ev )
{
	gi.dprintf( "%d", ev->GetInteger( 1 ) );
}

void ScriptThread::PrintFloat( Event *ev )
{
	gi.dprintf( "%.2f", ev->GetFloat( 1 ) );
}

void ScriptThread::PrintVector( Event *ev )
{
	Vector vec;

	vec = ev->GetVector( 1 );
	gi.dprintf( "(%.2f %.2f %.2f)", vec.x, vec.y, vec.z );
}

void ScriptThread::NewLine( Event *ev )
{
	gi.dprintf( "\n" );
}


void ScriptThread::UserPrint( Event *ev )
{
	gi.bprintf( PRINT_HIGH, "%s", ev->GetString( 1 ) );
}

void ScriptThread::UserPrintInt( Event *ev )
{
	gi.bprintf( PRINT_HIGH, "%d", ev->GetInteger( 1 ) );
}

void ScriptThread::UserPrintFloat( Event *ev )
{
	gi.bprintf( PRINT_HIGH, "%.2f", ev->GetFloat( 1 ) );
}

void ScriptThread::UserPrintVector( Event *ev )
{
	Vector vec;

	vec = ev->GetVector( 1 );
	gi.bprintf( PRINT_HIGH, "(%.2f %.2f %.2f)", vec.x, vec.y, vec.z );
}

void ScriptThread::UserNewLine( Event *ev )
{
	gi.bprintf( PRINT_HIGH, "\n" );
}

void ScriptThread::Assert( Event *ev )
{
	assert( ev->GetFloat( 1 ) );
}

void ScriptThread::Break( Event *ev )
{
	// Break into the debugger
	assert( 0 );
}

void ScriptThread::Clear( Event *ev )
{
	ScriptVariableList *vars;

	vars = Director.GetVarGroup( ev->GetToken( 1 ) );
	if ( vars )
	{
		vars->ClearList();
	}
}

EXPORT_FROM_DLL void ScriptThread::ScriptCallback( Event *ev )
{
	char name[ MAXTOKEN ];
	Entity  *other;
	Entity  *slave;

	if ( threadDying )
	{
		return;
	}

	slave = ev->GetEntity( 1 );
	strcpy( name, ev->GetString( 2 ) );
	other = ev->GetEntity( 3 );

	if ( !Goto( name ) )
	{
		ev->Error( "Label '%s' not found", name );
	}
	else
	{
		// kill any execute events (in case our last command was "wait")
		ClearWaitFor();
		// start right away
		Start( -1 );
	}
}

EXPORT_FROM_DLL void ScriptThread::ThreadCallback( Event *ev )
{
	ScriptThread *thread;

	if ( threadDying )
	{
		return;
	}

	thread = ev->GetThread();

	// should never happen, so catch it during development
	//assert( thread && ( thread == waitingForThread ) );

	if ( thread && ( thread == waitingForThread ) )
	{
		ClearWaitFor();
		// start right away
		Start( -1 );
	}
}

EXPORT_FROM_DLL void ScriptThread::ConsoleCallback( Event *ev )
{
	if ( threadDying )
	{
		return;
	}

	ClearWaitFor();
	// start right away
	Start( -1 );
}

EXPORT_FROM_DLL void ScriptThread::VariableCallback( Event *ev )
{
	ClearWaitFor();
	// start right away
	Start( -1 );
}

EXPORT_FROM_DLL void ScriptThread::DeathCallback( Event *ev )
{
	if ( threadDying )
	{
		return;
	}

	ClearWaitFor();
	// start right away
	Start( -1 );
}


EXPORT_FROM_DLL void ScriptThread::DoMove( void )
{
	int entnum;
	Entity   *ent;
	Event    *event;
	int count;
	int i;

	count = updateList.NumObjects();

	for( i = 1; i <= count; i++ )
	{
		entnum = ( int )updateList.ObjectAt( i );
		ent = G_GetEntity( entnum );
		if ( ent )
		{
			if ( ent->ValidEvent( EV_ProcessCommands ) )
			{
				event = new Event( EV_ProcessCommands );
				event->SetThread( this );
				ent->PostEvent( event, 0 );
			}
			else
			{
				// try to remove this from the update list
				if ( waitingNumObjects > 0 )
				{
					waitingNumObjects--;
				}
			}
		}
	}

	updateList.ClearObjectList();
}

void ScriptThread::TriggerEvent( Event *ev )
{
	const char  *name;
	Event          *event;
	Entity     *ent;
	TargetList  *tlist;
	int i;
	int num;

	name = ev->GetString( 1 );

	// Check for object commands
	if ( name && name[ 0 ] == '$' )
	{
		tlist = GetTargetList( str( name + 1 ) );
		num = tlist->list.NumObjects();
		for ( i = 1; i <= num; i++ )
		{
			ent = tlist->list.ObjectAt( i );

			assert( ent );

			event = new Event( EV_Activate );
			event->SetSource( EV_FROM_SCRIPT );
			event->SetThread( this );
			event->SetLineNumber( linenumber );
			event->AddEntity( world );
			ent->ProcessEvent( event );
		}
	}
	else if ( name[ 0 ] == '*' )   // Check for entnum commands
	{
		if ( !IsNumeric( &name[ 1 ] ) )
		{
			ScriptError( "Expecting numeric value for * command, but found '%s'\n", &name[ 1 ] );
		}
		else
		{
			ent = G_GetEntity( atoi( &name[ 1 ] ) );
			if ( ent )
			{
				event = new Event( EV_Activate );
				event->SetSource( EV_FROM_SCRIPT );
				event->SetThread( this );
				event->SetLineNumber( linenumber );
				event->AddEntity( world );
				ent->ProcessEvent( event );
			}
			else
			{
				ScriptError( "Entity not found for * command\n" );
			}
		}
		return;
	}
	else
	{
		ScriptError( "Invalid entity reference '%s'.\n", name );
	}
}

void ScriptThread::ServerEvent( Event *ev )
{
	int i, argc;
	const char *argv[ MAX_COMMANDS ];

	argc = 0;
	for ( i = 1; i <= ev->NumArgs(); i++ )
		argv[argc++] = ev->GetString( i );

	if (argc)
		ProcessCommand( argc, argv );
}

void ScriptThread::ClientEvent( Event *ev )
{
	//
	// do nothing
	//
}

void ScriptThread::SetLightStyle( int stylenum,const char *stylestring )
{
	gi.configstring( CS_LIGHTS + stylenum, stylestring );
}

void ScriptThread::EventSetLightStyle( Event *ev )
{
	if ( ev->NumArgs() < 2 )
	{
		ev->Error( "Too few arguments\n" );
		return;
	}
	SetLightStyle( ev->GetInteger( 1 ), ev->GetString( 2 ) );
}

void ScriptThread::CacheModel( Event *ev )
{
	const char *model;

	if ( !precache->value )
		return;
	model = ev->GetString( 1 );
	if ( model )
	{
		char str[ 128 ];

		// Prepend 'models/' to make things easier
		str[ 0 ] = 0;
		if ( !strchr( model, '*' ) && !strchr( model, '\\' ) && !strchr( model, '/' ) )
		{
			strcpy( str, "models/" );
		}
		strcat( str, model );
		gi.modelindex( str );
	}
}

void ScriptThread::CachePlayerModel( Event *ev )
{
	str model;
	Entity     *ent;
	Event      *event;
	qboolean forcecache = false;

	if ( ev->NumArgs() > 1 )
	{
		forcecache = ev->GetInteger( 2 );
	}

	if ( ( !deathmatch->value && !forcecache ) || !world )
	{
		return;
	}

	model = ev->GetString( 1 );

	// Create a new instance of this model and process the commands.
	// This will get the sounds and animations into the system.
	if ( model.length() )
	{
		// Add modelname to the list of valid player models
		game.ValidPlayerModels.AddObject( model );

		ent = new Entity;
		ent->setModel( model );
		event = new Event( EV_ProcessInitCommands );
		event->AddInteger( ent->edict->s.modelindex );
		ent->ProcessEvent( event );
	}

	delete ent;
}

//###
void ScriptThread::CacheBikeModel( Event *ev )
{
	str model;
	Entity     *ent;
	Event      *event;

	if ( !deathmatch->value )
	{
		return;
	}

	model = ev->GetString( 1 );

	// Create a new instance of this model and process the commands.
	// This will get the sounds and animations into the system.
	if ( model.length() )
	{
		// Add modelname to the list of valid player models
		game.ValidBikeModels.AddObject( model );

		ent = new Entity;
		ent->setModel( model );
		event = new Event( EV_ProcessInitCommands );
		event->AddInteger( ent->edict->s.modelindex );
		ent->ProcessEvent( event );
	}

	delete ent;
}
//###

void ScriptThread::CacheSound( Event *ev )
{
	const char *sound;

	if ( !precache->value )
		return;
	sound = ev->GetString( 1 );
	if ( sound )
		gi.soundindex( sound );
}

void ScriptThread::RegisterAlias( Event *ev )
{
	if (ev->NumArgs() < 3)
		gi.GlobalAlias_Add( ev->GetString( 1 ), ev->GetString( 2 ),  1 );
	else
		gi.GlobalAlias_Add( ev->GetString( 1 ), ev->GetString( 2 ), ev->GetInteger( 3 ) );
}

void ScriptThread::RegisterAliasAndCache( Event *ev )
{
	int length;
	const char * realname;

	realname = ev->GetString( 2 );

	if (ev->NumArgs() < 3)
		gi.GlobalAlias_Add( ev->GetString( 1 ), realname,  1 );
	else
		gi.GlobalAlias_Add( ev->GetString( 1 ), realname, ev->GetInteger( 3 ) );

	if ( !precache->value )
		return;

	length = strlen( realname );
	if ( (length > 4) && ( !strcmpi( &realname[ length-4 ], ".wav" ) ) )
		gi.soundindex( realname );
	else if ( (length > 4) && ( !strcmpi( &realname[ length-4 ], ".def" ) ) )
		gi.modelindex( realname );
}

void ScriptThread::MapEvent( Event *ev )
{
	G_BeginIntermission( ev->GetString( 1 ) );
	doneProcessing = true;
}

void ScriptThread::SetCvarEvent( Event *ev )
{
	str name;

	name = ev->GetString( 1 );
	if ( name != "" )
	{
		gi.cvar_set( name.c_str(), ev->GetString( 2 ) );
	}
}

void ScriptThread::CueCamera( Event *ev )
{
	Entity *ent;

	ent = ev->GetEntity( 1 );
	if ( ent )
	{
		SetCamera( ent );
	}
	else
	{
		ev->Error( "Camera named %s not found", ev->GetString( 1 ) );
	}
}

void ScriptThread::CuePlayer( Event *ev )
{
	SetCamera( NULL );
}

void ScriptThread::FreezePlayer( Event *ev )
{
	level.playerfrozen = true;
}

void ScriptThread::ReleasePlayer( Event *ev )
{
	level.playerfrozen = false;
}

void ScriptThread::AirClamp( Event *ev )
{
	level.airclamp = ev->GetInteger( 1 );
}

void ScriptThread::Spawn( Event *ev )
{
	Entity         *ent;
	Entity         *tent;
	const char      *name;
	ClassDef           *cls;
	char text[ 128 ];
	int n;
	int i;
	int num;
	const char     *targetname;
	ScriptVariable   *var;

	if ( ev->NumArgs() < 1 )
	{
		ev->Error( "Usage: spawn entityname [keyname] [value]..." );
		return;
	}

	// create a new entity
	G_InitSpawnArguments();

	name = ev->GetString( 1 );

	if ( name )
	{
		cls = getClassForID( name );
		if ( !cls )
		{
			cls = getClass( name );
		}

		if ( !cls )
		{
			str n;

			n = name;
			if ( !strstr( n.c_str(), ".def" ) )
			{
				n += ".def";
			}
			G_SetSpawnArg( "model", n.c_str() );
		}
		else
		{
			G_SetSpawnArg( "classname", name );
		}
	}

	if ( ev->NumArgs() > 2 )
	{
		n = ev->NumArgs();
		for( i = 2; i <= n; i += 2 )
		{
			G_SetSpawnArg( ev->GetString( i ), ev->GetString( i + 1 ) );
		}
	}

	cls = G_GetClassFromArgs();
	if ( !cls )
	{
		ev->Error( "'%s' is not a valid entity name", name );
		return;
	}

	// If there is a target set, then use that entity's origin and angles
	targetname = G_GetSpawnArg( "target", NULL );

	if ( targetname )
	{
		num = G_FindTarget( 0, targetname );
		if ( num )
		{
			tent = G_GetEntity( num );
			if (tent)
			{
				sprintf( text, "%f %f %f", tent->worldorigin[ 0 ], tent->worldorigin[ 1 ], tent->worldorigin[ 2 ] );
				G_SetSpawnArg( "origin", text );
				sprintf( text, "%f", tent->angles[1] );
				G_SetSpawnArg( "angle", text );
			}
			else
			{
				ev->Error( "Can't find entity with targetname %s", targetname );
			}
		}
		else
		{
			ev->Error( "Can't find targetname %s", targetname );
		}
	}

	ent = ( Entity * )cls->newInstance();
	var = Director.GetVariable( "parm.lastspawn" );
	var->setIntValue( ent->entnum );
	G_InitSpawnArguments();
}


void ScriptThread::DialogEvent( Event *ev )
{
	str name;
	float volume;
	int channel;
	float attenuation;
	float pitch;
	float timeofs;
	float fadetime;
	int flags;
	int i;
	str icon_name;
	str dialog_text;

	// Check to send text
	if ( ( dialog->value == 1 ) || ( dialog->value == 3 ) )
	{
		icon_name = ev->GetString( 1 );
		dialog_text = ev->GetString( 2 );
		SendDialog( icon_name.c_str(), dialog_text.c_str() );
	}

	// Check to send speech
	if ( ( dialog->value == 0 ) || ( dialog->value == 1 ) )
		return;

	//
	// set defaults
	//
	volume = 1.0f;
	channel = CHAN_DIALOG | CHAN_NO_PHS_ADD;
	attenuation = ATTN_NORM;
	pitch = 1.0f;
	timeofs = 0;
	fadetime = 0;
	flags = SOUND_SYNCH | SOUND_LOCAL_DIALOG;
	for ( i = 3; i <= ev->NumArgs(); i++ )
	{
		switch (i-3)
		{
		case 0:
			name = ev->GetString( i );
			break;
		case 1:
			volume = ev->GetFloat( i );
			break;
		case 2:
			channel = ev->GetInteger( i );
			break;
		case 3:
			attenuation = ev->GetFloat( i );
			break;
		case 4:
			pitch = ev->GetFloat( i );
			break;
		case 5:
			timeofs = ev->GetFloat( i );
			break;
		case 6:
			fadetime = ev->GetFloat( i );
			break;
		case 7:
			flags = ev->GetInteger( i );
			break;
		default:
			break;
		}
	}
	channel |= CHAN_NO_PHS_ADD;

	if ( name.length() )
	{
		//
		// we put in zero here because we aren't an entity
		//
		gi.sound( &g_edicts[ 0 ], channel, gi.soundindex( name.c_str() ), volume,
		          attenuation, timeofs, pitch, fadetime, flags );
	}
	else
	{
		ev->Error( "Null sound specified." );
	}
}

void ScriptThread::CrucialDialogEvent( Event *ev )
{
	str name;
	float volume;
	int channel;
	float attenuation;
	float pitch;
	float timeofs;
	float fadetime;
	int flags;
	int i;
	str icon_name;
	str dialog_text;

	// Check to send text
	icon_name = ev->GetString( 1 );
	dialog_text = ev->GetString( 2 );
	SendDialog( icon_name.c_str(), dialog_text.c_str() );

	//
	// set defaults
	//
	volume = 1.0f;
	channel = CHAN_DIALOG | CHAN_NO_PHS_ADD;
	attenuation = ATTN_NORM;
	pitch = 1.0f;
	timeofs = 0;
	fadetime = 0;
	flags = SOUND_SYNCH | SOUND_LOCAL_DIALOG;
	for ( i = 3; i <= ev->NumArgs(); i++ )
	{
		switch (i-3)
		{
		case 0:
			name = ev->GetString( i );
			break;
		case 1:
			volume = ev->GetFloat( i );
			break;
		case 2:
			channel = ev->GetInteger( i );
			break;
		case 3:
			attenuation = ev->GetFloat( i );
			break;
		case 4:
			pitch = ev->GetFloat( i );
			break;
		case 5:
			timeofs = ev->GetFloat( i );
			break;
		case 6:
			fadetime = ev->GetFloat( i );
			break;
		case 7:
			flags = ev->GetInteger( i );
			break;
		default:
			break;
		}
	}
	channel |= CHAN_NO_PHS_ADD;

	if ( name.length() )
	{
		//
		// we put in zero here because we aren't an entity
		//
		gi.sound( &g_edicts[ 0 ], channel, gi.soundindex( name.c_str() ), volume,
		          attenuation, timeofs, pitch, fadetime, flags );
	}
	else
	{
		ev->Error( "Null sound specified." );
	}
}

void ScriptThread::DialogSoundEvent( Event *ev )
{
	str name;
	float volume;
	int channel;
	float attenuation;
	float pitch;
	float timeofs;
	float fadetime;
	int flags;
	int i;

	// Check to send speech
	if ( ( dialog->value == 0 ) || ( dialog->value == 1 ) )
		return;

	//
	// set defaults
	//
	volume = 1.0f;
	channel = CHAN_DIALOG | CHAN_NO_PHS_ADD;
	attenuation = ATTN_NORM;
	pitch = 1.0f;
	timeofs = 0;
	fadetime = 0;
	flags = SOUND_SYNCH | SOUND_LOCAL_DIALOG;
	for ( i = 1; i <= ev->NumArgs(); i++ )
	{
		switch (i-1)
		{
		case 0:
			name = ev->GetString( i );
			break;
		case 1:
			volume = ev->GetFloat( i );
			break;
		case 2:
			channel = ev->GetInteger( i );
			break;
		case 3:
			attenuation = ev->GetFloat( i );
			break;
		case 4:
			pitch = ev->GetFloat( i );
			break;
		case 5:
			timeofs = ev->GetFloat( i );
			break;
		case 6:
			fadetime = ev->GetFloat( i );
			break;
		case 7:
			flags = ev->GetInteger( i );
			break;
		default:
			break;
		}
	}
	channel |= CHAN_NO_PHS_ADD;

	if ( name.length() )
	{
		//
		// we put in zero here because we aren't an entity
		//
		gi.sound( &g_edicts[ 0 ], channel, gi.soundindex( name.c_str() ), volume,
		          attenuation, timeofs, pitch, fadetime, flags );
	}
	else
	{
		ev->Error( "Null sound specified." );
	}
}


void ScriptThread::SetDialogScript( Event *ev )
{
	ScriptThread * pThread;
	ScriptLib.SetDialogScript( ev->GetString( 1 ) );
	pThread = Director.CreateThread( &script, "dialog::precache" );
	if ( pThread )
	{
		// start right away
		pThread->Start( -1 );
	}
}

void ScriptThread::FadeIn( Event *ev )
{
	float r,g,b,time;

	time = ev->GetFloat( 1 );
	r    = ev->GetFloat( 2 );
	g    = ev->GetFloat( 3 );
	b    = ev->GetFloat( 4 );

	gi.WriteByte( svc_console_command );
	gi.WriteString( va( "fi %0.2f %0.2f %0.2f %0.2f",time,r,g,b ) );
	gi.multicast( NULL, MULTICAST_ALL );
}

void ScriptThread::FadeOut( Event *ev )
{
	float r,g,b,time;

	time = ev->GetFloat( 1 );
	r    = ev->GetFloat( 2 );
	g    = ev->GetFloat( 3 );
	b    = ev->GetFloat( 4 );

	gi.WriteByte( svc_console_command );
	gi.WriteString( va( "fo %0.2f %0.2f %0.2f %0.2f",time,r,g,b ) );
	gi.multicast( NULL, MULTICAST_ALL );
}

void ScriptThread::Hud( Event *ev )
{
	int val;
	int j;
	edict_t     *other;

	val = !ev->GetInteger( 1 );

	for( j = 1; j <= game.maxclients; j++ )
	{
		other = &g_edicts[ j ];
		if ( other->inuse && other->client )
		{
			Player * client;
			Event * ev;

			client = ( Player * )other->entity;
			if ( val )
				ev = new Event( EV_Player_HideStats );
			else
				ev = new Event( EV_Player_DrawStats );
			client->ProcessEvent( ev );
		}
	}
}

void ScriptThread::Overlay( Event *ev )
{
	int val;
	int j;
	edict_t     *other;

	val = ev->GetInteger( 1 );

	for( j = 1; j <= game.maxclients; j++ )
	{
		other = &g_edicts[ j ];
		if ( other->inuse && other->client )
		{
			Player * client;
			Event * ev;

			client = ( Player * )other->entity;
			if ( val )
				ev = new Event( EV_Player_DrawOverlay );
			else
				ev = new Event( EV_Player_HideOverlay );
			client->ProcessEvent( ev );
		}
	}
}

void ScriptThread::LoadOverlay( Event *ev )
{
	SendOverlay( NULL, str( ev->GetString( 1 ) ) );
}

void ScriptThread::LoadIntermission( Event *ev )
{
	SendIntermission( NULL, str( ev->GetString( 1 ) ) );
}

void ScriptThread::IntermissionLayout( Event *ev )
{
	str layout;

	layout = ev->GetString( 1 );

	if (layout.length() > (MAX_LAYOUT_LENGTH - 5))
	{
		error( "ScriptThread::IntermissionLayout", "Max layout length exceeded for intermission layout.\n" );
	}

	gi.WriteByte( svc_console_command );
	gi.WriteString( va( "iml %s", layout.c_str() ) );
	gi.multicast( NULL, MULTICAST_ALL );
}

void ScriptThread::MusicEvent( Event *ev )
{
	const char *current;
	const char *fallback;

	current = NULL;
	fallback = NULL;
	current = ev->GetString( 1 );

	if ( ev->NumArgs() > 1 )
		fallback = ev->GetString( 2 );

	ChangeMusic( current, fallback, false );
}

void ScriptThread::ForceMusicEvent( Event *ev )
{
	const char *current;
	const char *fallback;

	current = NULL;
	fallback = NULL;
	current = ev->GetString( 1 );

	if ( ev->NumArgs() > 1 )
		fallback = ev->GetString( 2 );

	ChangeMusic( current, fallback, true );
}

void ScriptThread::SoundtrackEvent( Event *ev )
{
	ChangeSoundtrack( ev->GetString( 1 ) );
}

void ScriptThread::MenuEvent( Event *ev )
{
	gi.AddCommandString( va( "menu_load %s; menu_generic\n", ev->GetString( 1 ) ) );
}

void ScriptThread::SetCinematic( Event *ev )
{
	level.cinematic = true;
}

void ScriptThread::SetNonCinematic( Event *ev )
{
	level.cinematic = false;
}

void ScriptThread::SetSkipThread( Event *ev )
{
	world->skipthread = ev->GetString( 1 );
}

void ScriptThread::ScreenPrint( Event *ev )
{
	gi.WriteByte( svc_console_command );
	gi.WriteString( va( "spr %f %s\n", ev->GetFloat( 1 ), ev->GetString( 2 ) ) );
	gi.multicast( NULL, MULTICAST_ALL );
}

void ScriptThread::ScreenPrintFile( Event *ev )
{
	str path;

	path = ev->GetString( 1 );
	G_FixSlashes( path.c_str() );

	gi.WriteByte( svc_console_command );
	gi.WriteString( va( "spf %s\n", path.c_str() ) );
	gi.multicast( NULL, MULTICAST_ALL );
}

void ScriptThread::ClearScreenPrintFile( Event *ev )
{
	str path;

	gi.WriteByte( svc_console_command );
	gi.WriteString( va( "cspf\n" ) );
	gi.multicast( NULL, MULTICAST_ALL );
}

void ScriptThread::JC_Hearable( Event *ev )
{
	level.no_jc = false;
}

void ScriptThread::JC_Not_Hearable( Event *ev )
{
	level.no_jc = true;
}

void ScriptThread::MissionFailed( Event *ev )
{
	int i;

	ChangeMusic( "failure", "normal", true );

	for( i = 1; i <= game.maxclients; i++ )
	{
		if ( g_edicts[ i ].inuse )
		{
			gi.centerprintf ( &g_edicts[ i ], "jcx yv 20 string \"Mission Failed.\"" );
			if ( g_edicts[ i ].entity )
			{
				g_edicts[ i ].entity->PostEvent( EV_Player_Respawn, 3.0f );
			}
		}
	}

	level.missionfailed = true;
	level.missionfailedtime = level.time + 3;
}

void ScriptThread::PassToPathmanager( Event *ev )
{
	PathManager.ProcessEvent( ev );
}


void ScriptThread::StuffCommand( Event *ev )
{
	gi.AddCommandString( va( "%s\n", ev->GetString( 1 ) ) );
}

void ScriptThread::Training( Event *ev )
{
	level.training = ev->GetInteger( 1 );
}

void ScriptThread::ClearSaveGames( Event *ev )
{
	level.clearsavegames = true;
}

void ScriptThread::KillEnt( Event * ev )
{
	int num;
	Entity *ent;

	if ( ev->NumArgs() != 1 )
	{
		ev->Error( "No args passed in" );
		return;
	}

	num = ev->GetInteger( 1 );
	if ( ( num < 0 ) || ( num >= globals.max_edicts ) )
	{
		ev->Error( "Value out of range.  Possible values range from 0 to %d.\n", globals.max_edicts );
		return;
	}

	ent = G_GetEntity( num );
	ent->Damage( world, world, ent->max_health + 25, vec_zero, vec_zero, vec_zero, 0, 0, 0, -1, -1, 1 );
}

void ScriptThread::RemoveEnt( Event * ev )
{
	int num;
	Entity *ent;

	if ( ev->NumArgs() != 1 )
	{
		ev->Error( "No args passed in" );
		return;
	}

	num = ev->GetInteger( 1 );
	if ( ( num < 0 ) || ( num >= globals.max_edicts ) )
	{
		ev->Error( "Value out of range.  Possible values range from 0 to %d.\n", globals.max_edicts );
		return;
	}

	ent = G_GetEntity( num );
	ent->PostEvent( Event( EV_Remove ), 0 );
}

void ScriptThread::KillClass( Event * ev )
{
	int except;
	str classname;
	edict_t * from;
	Entity *ent;

	if ( ev->NumArgs() < 1 )
	{
		ev->Error( "No args passed in" );
		return;
	}

	classname = ev->GetString( 1 );

	except = 0;
	if ( ev->NumArgs() == 2 )
	{
		except = ev->GetInteger( 1 );
	}

	for ( from = &g_edicts[ game.maxclients + 1 ]; from < &g_edicts[ globals.num_edicts ]; from++ )
	{
		if ( !from->inuse )
		{
			continue;
		}

		assert( from->entity );

		ent = from->entity;

		if ( ent->entnum == except )
		{
			continue;
		}

		if ( ent->inheritsFrom( classname.c_str() ) )
		{
			ent->Damage( world, world, ent->max_health + 25, vec_zero, vec_zero, vec_zero, 0, 0, 0, -1, -1, 1 );
		}
	}
}

void ScriptThread::RemoveClass( Event * ev )
{
	int except;
	str classname;
	edict_t * from;
	Entity *ent;

	if ( ev->NumArgs() < 1 )
	{
		ev->Error( "No args passed in" );
		return;
	}

	classname = ev->GetString( 1 );

	except = 0;
	if ( ev->NumArgs() == 2 )
	{
		except = ev->GetInteger( 1 );
	}

	for ( from = &g_edicts[ game.maxclients + 1 ]; from < &g_edicts[ globals.num_edicts ]; from++ )
	{
		if ( !from->inuse )
		{
			continue;
		}

		assert( from->entity );

		ent = from->entity;

		if ( ent->entnum == except )
			continue;

		if ( ent->inheritsFrom( classname.c_str() ) )
		{
			ent->PostEvent( Event( EV_Remove ), 0 );
		}
	}
}

void ScriptThread::MapName( Event * ev )
{
	gi.configstring( CS_NAME, ev->GetString( 1 ) );
}

void ScriptThread::EndGame( Event * ev )
{
#ifdef SIN_ARCADE
	if ( !ARCADE_ComWriteByte( 0x06 ) )
		gi.error( "Could not send end of game to communications port" );
#endif
}

//###
void ScriptThread::TriggerFromEntEvent( Event *ev )
{
	const char  *from, *name;
	Event          *event;
	Entity     *ent, *froment;
	TargetList  *tlist;
	int i;
	int num;

	// get the entity that's supposed to cause the triggering
	from = ev->GetString( 1 );

	if(from && from[0] == '$')
	{
		tlist = GetTargetList( str( from + 1 ) );
		num = tlist->list.NumObjects();
		if(num)
		{
			froment = NULL;
			for(i = 1; i <= num && !froment; i++)
			{
				froment = tlist->list.ObjectAt( i );
			}
			assert( froment );
		}
		else
		{
			ScriptError( "Invalid from entity reference '%s'.\n", from );
			return;
		}
	}
	else if(from && from[0] == '*')   // Check for it being an entnum
	{
		if(!IsNumeric( &from[1] ))
		{
			ScriptError( "Expecting numeric value for * command, but found '%s'\n", &from[1] );
			return;
		}
		else
		{
			froment = G_GetEntity( atoi( &from[1] ));
			if(!froment)
			{
				ScriptError( "Entity not found for * command\n" );
				return;
			}
		}
	}
	else
	{
		ScriptError( "triggerfroment requires a '*' or '$' command as it's first parm.\n" );
		return;
	}

	name = ev->GetString( 2 );

	// Check for object commands
	if(name && name[0] == '$')
	{
		tlist = GetTargetList( str( name + 1 ) );
		num = tlist->list.NumObjects();
		for ( i = 1; i <= num; i++ )
		{
			ent = tlist->list.ObjectAt( i );

			assert( ent );

			event = new Event( EV_Activate );
			event->SetSource( EV_FROM_SCRIPT );
			event->SetThread( this );
			event->SetLineNumber( linenumber );
			event->AddEntity( froment );
			ent->ProcessEvent( event );
		}
	}
	else if (name && name[ 0 ] == '*' )   // Check for entnum commands
	{
		if ( !IsNumeric( &name[ 1 ] ) )
		{
			ScriptError( "Expecting numeric value for * command, but found '%s'\n", &name[ 1 ] );
		}
		else
		{
			ent = G_GetEntity( atoi( &name[ 1 ] ) );
			if ( ent )
			{
				event = new Event( EV_Activate );
				event->SetSource( EV_FROM_SCRIPT );
				event->SetThread( this );
				event->SetLineNumber( linenumber );
				event->AddEntity( froment );
				ent->ProcessEvent( event );
			}
			else
			{
				ScriptError( "Entity not found for * command\n" );
			}
		}
		return;
	}
	else
	{
		ScriptError( "Invalid entity reference '%s'.\n", name );
	}
}
//###