//-----------------------------------------------------------------------------
//
//  $Logfile:: /Quake 2 Engine/Sin/code/game/scriptmaster.cpp                 $
// $Revision:: 158                                                            $
//   $Author:: Markd                                                          $
//     $Date:: 10/28/99 10:43a                                                $
//
// 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.
//
// $Log:: /Quake 2 Engine/Sin/code/game/scriptmaster.cpp                      $
// 
// 158   10/28/99 10:43a Markd
// made a bunch of console commands cheat protected
// 
// 157   5/19/99 11:30a Markd
// Added new camera support 
// 
// 156   2/16/99 8:38p Jimdose
// moved sin arcade comm stuff to client
// 
// 155   12/18/98 11:03p Jimdose
// removed include of qcommon.h
// 
// 154   12/16/98 12:42a Aldie
// Added endgame command for Sin Arcade
// 
// 153   12/14/98 6:52p Aldie
// Added mapname command
// 
// 152   11/11/98 10:55p Jimdose
// Added RemoveEnt, RemoveClass, KillEnt, KillClass
// 
// 151   11/10/98 4:34p Jimdose
// added ClearSaveGames
// 
// 150   11/07/98 10:15p Markd
// put in forcemusic support
// 
// 149   11/07/98 10:05p Jimdose
// made spawn work with model names
// 
// 148   11/06/98 5:17p Jimdose
// added missionfailed and missionfailedtime to level vars
// when a mission has failed or the player is dead in single player, the game
// code immediately shows the loadmenu, preventing them from letting the game
// continue running if they exit the menu
// 
// 147   11/05/98 2:17a Aldie
// Added a check for the world to cacheplayermodel
// 
// 146   11/04/98 9:50p Aldie
// Added ability to force cache a player model
// 
// 145   10/27/98 9:46p Aldie
// Changed training cvar to level.training
// 
// 144   10/27/98 9:21p Markd
// fixed dialogsound bug
// 
// 143   10/27/98 8:07p Markd
// Added dialogsound command
// 
// 142   10/26/98 9:06p Aldie
// Added stuffcommand
// 
// 141   10/26/98 5:15p Jimdose
// changed recalcpaths to passtopathmanager
// 
// 140   10/26/98 4:42p Jimdose
// added recalcpaths
// 
// 139   10/26/98 2:16p Aldie
// Added AirClamp
// 
// 138   10/26/98 12:42a Markd
// added new print commands and crucial dialog
// 
// 137   10/26/98 12:05a Jimdose
// added MissionFailed
// 
// 136   10/25/98 6:26p Markd
// Added in no_jc abililty
// 
// 135   10/24/98 9:38p Jimdose
// Added GetUniqueThreadNumber
// Made TerminateThread not kill actor threads or thread 0 (an invalid thread)
// 
// 134   10/24/98 12:42a Markd
// changed origins to worldorigins where appropriate
// 
// 133   10/22/98 5:44p Jimdose
// MapEvent now sets doneProcessing to true
// 
// 132   10/22/98 4:32p Aldie
// Added a clear screen print file command
// 
// 131   10/19/98 11:51p Aldie
// Added fixslashes to screen print file
// 
// 130   10/19/98 12:07a Jimdose
// made all code use fast checks for inheritance (no text lookups when
// possible)
// isSubclassOf no longer requires ::_classinfo()
// 
// 129   10/18/98 7:59p Aldie
// Added screenprintfile
// 
// 128   10/14/98 12:12a Aldie
// Added a bunch of intermission commands
// 
// 127   10/12/98 9:07p Markd
// Made it so playermodels are not cached if not in deathmatch
// 
// 126   10/08/98 3:44p Markd
// re-merged my changes back into the checked in file
// 
// 125   10/08/98 12:02a Jimdose
// made GameTime a ScriptVariablePtr
// moved PlayerFrozen to game
// got rid of type in gamescript
// 
// 124   10/01/98 8:00p Markd
// Made dialog use NO_PHS
// 
// 123   9/26/98 7:17p Markd
// Made WaitFor support more than one object with the same target name
// 
// 122   9/25/98 4:36p Markd
// Replaced Event::Find with Event::Exists
// 
// 121   9/23/98 11:00p Markd
// put in some garbage collection on stuff that wasn't freed up
// 
// 120   9/22/98 4:23p Markd
// Fixed some targetname stuff for lights, doors and scriptobjects
// 
// 119   9/19/98 6:13p Aldie
// Added printscreen command
// 
// 118   9/15/98 7:43p Markd
// fixed some typos
// 
// 117   9/15/98 7:35p Markd
// Moved music and soundtrack stuff to specialfx
// 
// 116   9/15/98 6:46p Aldie
// Clear the waitfor's before deleting threads
// 
// 115   9/10/98 8:34p Markd
// removed drawoverlay and drawstats variables from level structure
// 
// 114   9/09/98 3:56p Aldie
// Changed precision of fade floats
// 
// 113   9/02/98 7:47p Aldie
// Added cacheplayermodel function
// 
// 112   8/29/98 9:45p Jimdose
// Changed BeginIntermission to G_BeginIntermission
// 
// 111   8/29/98 7:22p Aldie
// Updated the spawn function
// 
// 110   8/29/98 5:27p Markd
// added specialfx, replaced misc with specialfx where appropriate
// 
// 109   8/26/98 6:43p Jimdose
// fixed bug in Restore
// 
// 108   8/24/98 11:32a Markd
// Added Start method to threads, repladed all ProcessEvent(
// EV_ScriptThread_execute) with thread->Start( -1 )
// 
// 107   8/22/98 10:04p Markd
// added noprecache support for cachemodel and cachesound
// 
// 106   8/22/98 6:34p Markd
// Added Terminate extened behavior
// 
// 105   8/22/98 1:22a Markd
// Added WaitForPlayer support
// 
// 104   8/21/98 5:26p Markd
// Added sv_precache and cl_precache 
// 
// 103   8/21/98 1:43a Markd
// Changed parameters to CreateThread
// 
// 102   8/20/98 4:39p Markd
// Added print vector support
// 
// 101   8/19/98 6:38p Markd
// Added noncinematic event
// 
// 100   8/18/98 11:08p Markd
// Added new Alias System
// 
// 99    8/17/98 7:31p Jimdose
// Added WaitingFor to test if a thread is waiting for the specified entity
// 
// 98    8/17/98 4:34p Markd
// Added cinematic and skipthread events
// 
// 97    8/15/98 11:18p Jimdose
// Restore was always starting the thread up.
// 
// 96    8/15/98 5:31p Jimdose
// Added SetScript to ScriptThread for changing script files without using a
// Goto
// 
// 95    8/14/98 9:09p Jimdose
// added waitUntil to keep track of wait times when marking and restoring (used
// when changing actor states)
// 
// 94    8/14/98 8:14p Aldie
// Added generic overlay system
// 
// 93    8/14/98 3:44p Jimdose
// WaitingFor variables are now all cleared after they're no longer valid
// 
// 92    8/13/98 7:45p Jimdose
// EventEnd now sets doneprocessing to true when doing the callback for model
// scripts
// 
// 91    8/13/98 7:29p Jimdose
// Added type to scriptthreads
// 
// 90    8/13/98 5:00p Jimdose
// ProcessCommand checks if variables are empty before using them with
// commands.
// 
// 89    8/12/98 6:43p Jimdose
// Changed TriggerEvent so that it doesn't add the entities that were triggered
// to the updateList.  This would cause them to eroniously recieve doMove
// commands, overriding their real movethread information.
// 
// 88    8/08/98 7:51p Jimdose
// changed realWorld to world
// 
// 87    8/07/98 5:44p Jimdose
// Process command now uses isVariableCommand to check if a command should be
// sent to a variable as apposed to the object that variable refers to
// 
// 86    8/06/98 10:49p Jimdose
// fixed index out of range bug in ProcessCommand 
// 
// 85    8/06/98 6:59p Jimdose
// Added GameTime
// 
// 84    8/06/98 5:18p Jimdose
// Added ProcessCommandFromEvent
// 
// 83    7/31/98 8:09p Jimdose
// Script commands now include flags to indicate cheats and console commands
// 
// 82    7/26/98 12:12a Markd
// Added precache ability to dialog stuff
// 
// 81    7/25/98 4:37p Aldie
// Menu controls
// 
// 80    7/24/98 6:18p Aldie
// enable and disable huds
// 
// 79    7/24/98 4:52p Jimdose
// Added setcvar command to script
// 
// 78    7/24/98 2:11p Markd
// Changed dialog from VOICE to DIALOG
// 
// 77    7/21/98 9:06p Markd
// Added music and soundtrack events
// 
// 76    7/21/98 4:14p Jimdose
// waitFor now adds the object to the update list so that if no commands have
// been issued, the object will give a moveDone right away
// Made DoMove post the EV_ProcessCommands event so that any callbacks to the
// thread don't clear the updateList
// 
// 75    7/20/98 5:48p Jimdose
// Added map command for changing levels
// 
// 74    7/18/98 7:36p Aldie
// Added support for fades
// 
// 73    7/16/98 5:18p Aldie
// Fixed error message bug.
// 
// 72    7/14/98 11:51p Jimdose
// EventEnd only notifies the owning model if the event comes from script
// 
// 71    7/11/98 9:24p Markd
// Forgot to add SetDialogScript event to ScriptThread
// 
// 70    7/11/98 8:43p Markd
// Added SetDialogScript, added optional label support to CreateThread
// 
// 69    7/11/98 2:49p Markd
// Added dialog event
// 
// 68    7/09/98 1:39a Jimdose
// updateList is now cleared everytime we execute the script to insure that it
// always only contains THIS frame's objects
// 
// 67    7/07/98 11:36p Jimdose
// Added error for unsupported global commands
// made end command signal self if model script
// added ThreadMarker
// 
// 66    7/03/98 4:10p Jimdose
// Added assert and break
// 
// 65    6/27/98 9:48p Jimdose
// Fixed a bug with TriggerEvent
// 
// 64    6/27/98 7:03p Markd
// Added WaitForSound
// 
// 63    6/25/98 8:14p Jimdose
// Made waitFor use *-entnum as well as targetname
// 
// 62    6/24/98 6:47p Jimdose
// Made entity number based commands use "*" prefix consistantly
// 
// 61    6/18/98 8:47p Jimdose
// Added better event error handling
// Added source info to events
// 
// 60    6/17/98 3:03p Markd
// Changed NumArgs back to previous behavior
// 
// 59    6/10/98 9:38p Jimdose
// Since we changed waitingFor to an str, ObjectMoveDone has to check if the
// length of waitingFor is > 0 before comaring it to the object's targetname,
// otherwise, calls to this function can cause threads that aren't waiting for
// objects to continue.
// 
// 58    6/10/98 7:53p Markd
// Made NumArgs behave correctly like argc
// 
// 57    6/10/98 6:08p Jimdose
// Made ProcessCommand add the thread to * commands
// 
// 56    6/09/98 4:22p Jimdose
// Added multi-file scripting
// each script now has its own set of labels that are only processed once
// 
// 55    6/04/98 11:07a Markd
// Made sure to initialize ScriptMaster when CloseScript is called
// 
// 54    5/27/98 5:03a Aldie
// Added warning for ent not found in * commands
// 
// 53    5/26/98 11:59p Markd
// put label support into ScriptMaster
// 
// 52    5/26/98 9:25p Aldie
// Added spawn command
// 
// 51    5/26/98 7:55p Jimdose
// added scripted cameras
// 
// 50    5/26/98 4:46p Aldie
// Added player commands
// 
// 49    5/26/98 4:22p Markd
// Added TargetList stuff and sped up SendToSlaves function
// 
// 48    5/25/98 1:39p Aldie
// Added create console user
// 
// 47    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
// 
// 46    5/24/98 4:48p Jimdose
// Made char *'s const
// 
// 45    5/22/98 10:57a Markd
// Put in label support parsing
// 
// 44    5/20/98 10:00p Jimdose
// Rewrote KillThread so that it didn't remove the thread from the list before
// calling the destructor, thus causing assertion failures
// 
// 43    5/20/98 5:30p Jimdose
// Added threadDying to prevent threads from executing after an "end" was
// reached.  There was a bug where a thread had a callback on the same frame
// that it was killed that caused it to execute code from another thread.
// 
// 42    5/20/98 4:31p Markd
// bullet-proofed thread code a bit
// 
// 41    5/19/98 4:24p Aldie
// Added DeathCallback and WaitForDeath
// 
// 40    5/13/98 4:57p Jimdose
// EventEnd now posts remove 0 in the future
// 
// 39    5/11/98 5:53p Markd
// Added aliascache command
// 
// 38    5/08/98 2:56p Markd
// Added alias event
// 
// 37    5/05/98 3:46p Aldie
// Uncommented surfaceManager stuff.
// 
// 36    5/04/98 8:31p Markd
// Added setlightstyle, cachemodel and cachesound
// 
// 35    5/04/98 8:11p Aldie
// Updated surfaceManager name
// 
// 34    5/02/98 8:47p Markd
// Added server and client event prefixes also modified CreateThread stuff
// 
// 33    5/01/98 5:05p Jimdose
// Added TriggerEvent
// 
// 32    4/03/98 1:10p Aldie
// Added waitForVariable
// 
// 31    3/28/98 4:35p Jimdose
// Disabled assertion in threadCallback
// 
// 30    3/28/98 3:28p Aldie
// Fixed some console input problems.
// 
// 29    3/27/98 6:46p Jimdose
// Made EventEnd notify waiting threads that it was ending
// 
// 28    3/27/98 6:23p Aldie
// Added some better console input stuff.
// 
// 27    3/26/98 7:15p Aldie
// Added console type vars.
// 
// 26    3/26/98 2:52p Aldie
// Added interface between consoles and scripts.
// 
// 25    3/24/98 8:54p Jimdose
// Added thread management script commands
// 
// 24    3/24/98 4:59p Jimdose
// Fixed various bugs related to the new event system
// 
// 23    3/23/98 1:45p Jimdose
// Fixed bug where GetThread was returning the wrong thread
// 
// 22    3/23/98 1:32p Jimdose
// Revamped event and command system
// Separated threads from controlling object for better thread management
// 
// 21    3/07/98 5:17p Aldie
// Fixed trans_mag and trans_angle.
// 
// 20    3/02/98 8:49p Jimdose
// Changed the classid parameter of CLASS_DECLARATION to a quoted string so
// that you could have a NULL classid.
// 
// 19    2/28/98 4:43p Aldie
// Added Matrix4TransformVector
// 
// 18    2/25/98 2:24p Aldie
// Updated texinfo command system.
// 
// 17    2/24/98 3:38p Aldie
// Added support for texinfo commands.
// 
// 16    2/21/98 1:10p Jimdose
// Changed initThread so that it doesn't search for a label if a NULL pointer
// is passed in and added a parameter to determine if it should execute the
// code immediately.
// 
// 15    2/18/98 8:05p Jimdose
// GetObject replaces getObjectFromScrip, getScriptObject, and FindEntity
// Commands are now sent to all entities with a specified targetname, rather
// than just the first one.
// 
// 
// 13    2/18/98 4:40p Jimdose
// Syntax for conditionals changed from "var condition value label" to "var
// condition value statement"
// Added check for non-numeric values in variables in GetFloat, GetDouble, and
// GetInt
// 
// 12    2/17/98 7:08p Jimdose
// Rewrote script variable support.  New support KICKS ASS. :)
// 
// 11    2/06/98 5:38p Jimdose
// Added check for missing file in setScript
// 
// 10    2/03/98 10:48a Jimdose
// Updated to work with Quake 2 engine
// Got rid of preprocessing stage.
// 
// 8     11/20/97 9:02p Jimdose
// Changed Container class to use ints instead of void *
// 
// 7     11/07/97 6:41p Jimdose
// Changed DoMove to remove entities from the updateList more efficiently.
// Added support for variables.
// 
// 6     10/28/97 4:22p Jimdose
// Made work with any Entity instead of just ScriptSlaves.
// Moved all general entity commands into Entity.  ScriptMaster now only
// handles script flow commands.
// updateList only contains the entities that received commands for the current
// frame and is then clear when processing for that iteration is done.
// 
// 5     10/27/97 3:30p Jimdose
// Removed dependency on quakedef.h
// 
// 4     10/06/97 6:36p Jimdose
// Added control over dynamic lights.
// 
// 3     9/26/97 5:23p Jimdose
// Added standard Ritual headers
//
// 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"

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 | EV_CHEAT );
Event EV_ScriptThread_KillClass( "killclass", EV_CONSOLE | EV_CHEAT );
Event EV_ScriptThread_RemoveEnt( "removeent", EV_CONSOLE | EV_CHEAT );
Event EV_ScriptThread_RemoveClass( "removeclass", EV_CONSOLE | EV_CHEAT );

// 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_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" );
Event EV_ScriptThread_CameraCommand( "cam" );

// 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();
	}

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

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;
	}

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;
	}


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);
               }
            }
         }
      }
   }

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

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

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 );
            }
         }
      }
   }

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 );
      }
   }



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);
               }
            }
         }
      }
   }

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);
               }
            }
         }
      }
   }

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;
	}

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;
	}

ScriptThread *ScriptMaster::CurrentThread
	(
	void
	)

	{
	return currentThread;
	}

void ScriptMaster::SetCurrentThread
	(
	ScriptThread *thread
	)

	{
	currentThread = thread;
	}

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?
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;
	}

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;
	}

int ScriptMaster::GetUniqueThreadNumber
	(
	void
	)

	{
   do
      {
      threadIndex++;
      if ( threadIndex == 0 )
         {
         threadIndex = 1;
         }
      }
   while( GetThread( threadIndex ) );
   
   return threadIndex;
	}

qboolean ScriptMaster::labelExists
	(
   GameScript * scr,
	const char *name
	)

	{
	return scr->labelExists( name );
	}

qboolean ScriptMaster::Goto
	(
   GameScript * scr,
	const char *name
	)

	{
	return scr->Goto( name );
	}

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;
	}

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 );
	}

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_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_CameraCommand,   ( Response )&ScriptThread::CameraCommand },
		{ NULL, NULL }
	};

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

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

void ScriptThread::ClearWaitFor
	(
	void
	)

	{
	waitUntil = 0;
	waitingFor = "";
	waitingNumObjects = 0;
	waitingForThread = NULL;
   waitingForConsole = "";
   waitingForVariable = "";
   waitingForDeath = "";
   waitingForPlayer = false;
	}

void ScriptThread::SetType
	(
	scripttype_t newtype
	)

	{
	type = newtype;
	}

scripttype_t ScriptThread::GetType
	(
	void
	)

	{
	return type;
	}

int ScriptThread::ThreadNum
	(
	void
	)

	{
	return threadNum;
	}

const char *ScriptThread::ThreadName
	(
	void
	)

	{
	return threadName.c_str();
	}

int ScriptThread::CurrentLine
	(
	void
	)

	{
	return linenumber;
	}

const char *ScriptThread::Filename
	(
	void
	)

	{
	return script.Filename();
	}

ScriptThread *ScriptThread::WaitingOnThread
	(
	void
	)

	{
	return waitingForThread;
	}

const char *ScriptThread::WaitingOnConsole
	(
	void
	)

	{
	return waitingForConsole.c_str();
	}

const char *ScriptThread::WaitingOnVariable
	(
	void
	)

	{
	return waitingForVariable.c_str();
	}

const char *ScriptThread::WaitingOnDeath
	(
	void
	)

	{
	return waitingForDeath.c_str();
	}

qboolean ScriptThread::WaitingOnPlayer
	(
	void
	)

	{
   return waitingForPlayer;
	}

ScriptVariableList *ScriptThread::Vars
	(
	void
	)

	{
	return &localVars;
	}


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;
	}

qboolean ScriptThread::SetScript
	(
	const char *name
	)

	{
	GameScript *scr;

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

	return false;
	}

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;
	}

qboolean ScriptThread::labelExists
	(
	const char *name
	)

	{
   return ScriptLib.labelExists( &script, name );
	}

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 );
	}

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 );
		}
	}

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;
	}

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;
	}

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() );
			}
		}
	}

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 );
	}

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

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 );
	}

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
// 
//****************************************************************************************

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;
	}

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();
		}
	}

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 );
		}
	}

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 );
		}
	}

void ScriptThread::ConsoleCallback
	(
	Event *ev
	)

	{
	if ( threadDying )
		{
		return;
		}

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

void ScriptThread::VariableCallback
	(
	Event *ev
	)

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

void ScriptThread::DeathCallback
	(
	Event *ev
	)

	{
	if ( threadDying )
		{
		return;
		}

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


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::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
   gi.WriteByte( svc_stufftext );
   gi.WriteString( "gameover" );
   gi.multicast( NULL, MULTICAST_ALL );
#endif
   }

void ScriptThread::CameraCommand
	(
   Event * ev
   )

   {
   Event *e;
	const char *cmd;
   int   i;
   int   n;

   if ( !ev->NumArgs() )
      {
      ev->Error( "Usage: cam [command] [arg 1]...[arg n]" );
      return;
      }

	cmd = ev->GetString( 1 );
  	if ( Event::Exists( cmd ) )
		{
		e = new Event( cmd );
		e->SetSource( EV_FROM_SCRIPT );
		e->SetThread( this );
		e->SetLineNumber( linenumber );

      n = ev->NumArgs();
		for( i = 2; i <= n; i++ )
			{
			e->AddToken( ev->GetToken( i ) );
			}

      CameraMan.ProcessEvent( e );
      }
   else
      {
      ev->Error( "Unknown camera command '%s'.\n", cmd );
      }
   }
