//===========================================================================
//
// Name:			ai_cast_script.c
// Function:		Wolfenstein AI Character Scripting
// Programmer:		Ridah
// Tab Size:		4 (real tabs)
//===========================================================================

#include "../game/g_local.h"
#include "../game/q_shared.h"
#include "../game/botlib.h"		//bot lib interface
#include "../game/be_aas.h"
#include "../game/be_ea.h"
#include "../game/be_ai_gen.h"
#include "../game/be_ai_goal.h"
#include "../game/be_ai_move.h"
#include "../botai/botai.h"			//bot ai interface

#include "ai_cast.h"

/*
Scripting that allows the designers to control the behaviour of AI characters
according to each different scenario.
*/

// action functions need to be declared here so they can be accessed in the scriptAction table
qboolean AICast_ScriptAction_GotoMarker( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_WalkToMarker( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_CrouchToMarker( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_GotoCast( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_WalkToCast( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_CrouchToCast( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_Wait( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_Trigger( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_FollowCast( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_PlaySound( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_NoAttack( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_Attack( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_PlayAnim( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_ClearAnim( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_SetAmmo( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_SetClip( cast_state_t *cs, char *params );	//----(SA)	added
qboolean AICast_ScriptAction_SelectWeapon( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_GiveArmor( cast_state_t *cs, char *params );	//----(SA)	added
qboolean AICast_ScriptAction_GiveWeapon( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_GiveInventory( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_TakeWeapon( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_Movetype( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_AlertEntity( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_SaveGame( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_FireAtTarget( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_GodMode( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_Accum( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_SpawnCast( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_MissionFailed( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_MissionSuccess( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_NoAIDamage( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_Print( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_FaceTargetAngles( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_ResetScript( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_Mount( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_Unmount( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_SavePersistant( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_ChangeLevel( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_FoundSecret( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_NoSight( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_Sight( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_NoAvoid( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_Avoid( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_Attrib( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_DenyAction( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_LightningDamage( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_Headlook( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_BackupScript( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_RestoreScript( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_StateType( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_KnockBack( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_Zoom( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_Parachute( cast_state_t *cs, char *params );
qboolean AICast_ScriptAction_StartCam( cast_state_t *cs, char *params);	//----(SA)	added
qboolean AICast_ScriptAction_StartCamBlack( cast_state_t *cs, char *params);	//----(SA)	added
qboolean AICast_ScriptAction_EntityScriptName( cast_state_t *cs, char *params);
qboolean AICast_ScriptAction_AIScriptName( cast_state_t *cs, char *params);
qboolean AICast_ScriptAction_SetHealth( cast_state_t *cs, char *params);
qboolean AICast_ScriptAction_NoTarget( cast_state_t *cs, char *params);
qboolean AICast_ScriptAction_Cvar( cast_state_t *cs, char *params);

// these are the actions that each event can call
cast_script_stack_action_t scriptActions[] =
{
	{"gotomarker",		AICast_ScriptAction_GotoMarker},
	{"runtomarker",		AICast_ScriptAction_GotoMarker},
	{"walktomarker",	AICast_ScriptAction_WalkToMarker},
	{"crouchtomarker",	AICast_ScriptAction_CrouchToMarker},
	{"gotocast",		AICast_ScriptAction_GotoCast},
	{"runtocast",		AICast_ScriptAction_GotoCast},
	{"walktocast",		AICast_ScriptAction_WalkToCast},
	{"crouchtocast",	AICast_ScriptAction_CrouchToCast},
	{"followcast",		AICast_ScriptAction_FollowCast},
	{"playsound",		AICast_ScriptAction_PlaySound},
	{"playanim",		AICast_ScriptAction_PlayAnim},
	{"clearanim",		AICast_ScriptAction_ClearAnim},
	{"wait",			AICast_ScriptAction_Wait},
	{"trigger",			AICast_ScriptAction_Trigger},
	{"setammo",			AICast_ScriptAction_SetAmmo},
	{"setclip",			AICast_ScriptAction_SetClip},	//----(SA)	added
	{"selectweapon",	AICast_ScriptAction_SelectWeapon},
	{"noattack",		AICast_ScriptAction_NoAttack},
	{"attack",			AICast_ScriptAction_Attack},
	{"givearmor",		AICast_ScriptAction_GiveArmor},	//----(SA)	added
	{"giveinventory",	AICast_ScriptAction_GiveInventory},
	{"giveweapon",		AICast_ScriptAction_GiveWeapon},
	{"takeweapon",		AICast_ScriptAction_TakeWeapon},
	{"movetype",		AICast_ScriptAction_Movetype},
	{"alertentity",		AICast_ScriptAction_AlertEntity},
	{"savegame",		AICast_ScriptAction_SaveGame},
	{"fireattarget",	AICast_ScriptAction_FireAtTarget},
	{"godmode",			AICast_ScriptAction_GodMode},
	{"accum",			AICast_ScriptAction_Accum},
	{"spawncast",		AICast_ScriptAction_SpawnCast},
	{"missionfailed",	AICast_ScriptAction_MissionFailed},
	{"missionsuccess",	AICast_ScriptAction_MissionSuccess},
	{"noaidamage",		AICast_ScriptAction_NoAIDamage},
	{"print",			AICast_ScriptAction_Print},
	{"facetargetangles",AICast_ScriptAction_FaceTargetAngles},
	{"resetscript",		AICast_ScriptAction_ResetScript},
	{"mount",			AICast_ScriptAction_Mount},
	{"unmount",			AICast_ScriptAction_Unmount},
	{"savepersistant",	AICast_ScriptAction_SavePersistant},
	{"changelevel",		AICast_ScriptAction_ChangeLevel},
	{"foundsecret",		AICast_ScriptAction_FoundSecret},
	{"nosight",			AICast_ScriptAction_NoSight},
	{"sight",			AICast_ScriptAction_Sight},
	{"noavoid",			AICast_ScriptAction_NoAvoid},
	{"avoid",			AICast_ScriptAction_Avoid},
	{"attrib",			AICast_ScriptAction_Attrib},
	{"denyactivate",	AICast_ScriptAction_DenyAction},
	{"lightningdamage",	AICast_ScriptAction_LightningDamage},
	{"deny",			AICast_ScriptAction_DenyAction},
	{"headlook",		AICast_ScriptAction_Headlook},
	{"backupscript",	AICast_ScriptAction_BackupScript},
	{"restorescript",	AICast_ScriptAction_RestoreScript},
	{"statetype",		AICast_ScriptAction_StateType},
	{"knockback",		AICast_ScriptAction_KnockBack},
	{"zoom",			AICast_ScriptAction_Zoom},
	{"parachute",		AICast_ScriptAction_Parachute},
	{"startcam",		AICast_ScriptAction_StartCam},	//----(SA)	added
	{"startcamblack",	AICast_ScriptAction_StartCamBlack},	//----(SA)	added
	{"entityscriptname",AICast_ScriptAction_EntityScriptName},
	{"aiscriptname",	AICast_ScriptAction_AIScriptName},
	{"sethealth",		AICast_ScriptAction_SetHealth},
	{"notarget",		AICast_ScriptAction_NoTarget},
	{"cvar",			AICast_ScriptAction_Cvar},

	{NULL,				NULL}
};

qboolean AICast_EventMatch_StringEqual( cast_script_event_t *event, char *eventParm );
qboolean AICast_EventMatch_IntInRange( cast_script_event_t *event, char *eventParm );

// the list of events that can start an action sequence
// NOTE!!: only append to this list, DO NOT INSERT!!
cast_script_event_define_t	scriptEvents[] =
{
	{"spawn",			NULL},			// called as each character is spawned into the game
	{"enemysight",		AICast_EventMatch_StringEqual},	// enemy has been sighted for the first time (once only)
	{"sight",			AICast_EventMatch_StringEqual},	// non-enemy has been sighted for the first time (once only)
	{"enemydead",		AICast_EventMatch_StringEqual},	// our enemy is now dead
	{"trigger",			AICast_EventMatch_StringEqual},	// something has triggered us (always followed by an identifier)
	{"pain",			AICast_EventMatch_IntInRange},	// we've been hurt
	{"death",			NULL},			// RIP
	{"activate",		AICast_EventMatch_StringEqual},	// "param" has just activated us
	{"enemysightcorpse",AICast_EventMatch_StringEqual},	// sighted the given enemy as a corpse, for the first time
	{"friendlysightcorpse", NULL},						// sighted a friendly as a corpse for the first time
	{"avoiddanger",		AICast_EventMatch_StringEqual},	// we are avoiding something dangerous
	{"blocked",			AICast_EventMatch_StringEqual},	// blocked by someone else
	{"statechange",		AICast_EventMatch_StringEqual},	// changing aistates
	{"bulletimpact",	NULL},
	{"inspectbodystart",	AICast_EventMatch_StringEqual},	// starting to travel to body for inspection
	{"inspectbodyend",	AICast_EventMatch_StringEqual},	// reached body for inspection
	{"inspectsoundstart",	AICast_EventMatch_StringEqual},	// reached sound for inspection
	{"inspectsoundend",	AICast_EventMatch_StringEqual},	// reached sound for inspection
	{"attacksound",		AICast_EventMatch_StringEqual},	// play a custom attack sound, and/or deny playing the default sound
	{"fakedeath",		NULL},
	{"bulletimpactsound",	NULL},
	{"inspectfriendlycombatstart", NULL},

	{NULL,				NULL}
};

int	numSecrets;

/*
===============
AICast_EventMatch_StringEqual
===============
*/
qboolean AICast_EventMatch_StringEqual( cast_script_event_t *event, char *eventParm )
{
	if (!event->params || !event->params[0] || (eventParm && !Q_strcasecmp( event->params, eventParm )))
		return qtrue;
	else
		return qfalse;
}

/*
===============
AICast_EventMatch_IntInRange
===============
*/
qboolean AICast_EventMatch_IntInRange( cast_script_event_t *event, char *eventParm )
{
	char *pString, *token;
	int	int1, int2, eInt;

	// get the cast name
	pString = eventParm;
	token = COM_ParseExt( &pString, qfalse );
	int1 = atoi( token );
	token = COM_ParseExt( &pString, qfalse );
	int2 = atoi( token );

	eInt = atoi( event->params );

	if (eventParm && eInt > int1 && eInt <= int2)
		return qtrue;
	else
		return qfalse;
}

/*
===============
AICast_EventForString
===============
*/
int AICast_EventForString( char *string )
{
	int i;

	for (i=0; scriptEvents[i].eventStr; i++)
	{
		if (!Q_strcasecmp( string, scriptEvents[i].eventStr ))
			return i;
	}

	return -1;
}

/*
===============
AICast_ActionForString
===============
*/
cast_script_stack_action_t *AICast_ActionForString( char *string )
{
	int i;

	for (i=0; scriptActions[i].actionString; i++)
	{
		if (!Q_strcasecmp( string, scriptActions[i].actionString )) {
			if (!Q_strcasecmp( string, "foundsecret" )) {
				numSecrets++;
			}
			return &scriptActions[i];
		}
	}

	return NULL;
}

/*
=============
AICast_ScriptLoad

  Loads the script for the current level into the buffer
=============
*/
void AICast_ScriptLoad( void )
{
	char			filename[MAX_QPATH];
	vmCvar_t		mapname;
	fileHandle_t	f;
	int				len;

	level.scriptAI = NULL;

	trap_Cvar_VariableStringBuffer( "ai_scriptName", filename, sizeof(filename) );
	if (strlen( filename ) > 0) {
		trap_Cvar_Register( &mapname, "ai_scriptName", "", CVAR_ROM );
	} else {
		trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM );
	}
	Q_strncpyz( filename, "maps/", sizeof(filename) );
	Q_strcat( filename, sizeof(filename), mapname.string );
	Q_strcat( filename, sizeof(filename), ".ai" );

	len = trap_FS_FOpenFile( filename, &f, FS_READ );

	// make sure we clear out the temporary scriptname
	trap_Cvar_Set( "ai_scriptName", "" );

	if (len < 0)
		return;

	level.scriptAI = G_Alloc( len );
	trap_FS_Read( level.scriptAI, len, f );

	trap_FS_FCloseFile( f );

	return;
}

/*
==============
AICast_ScriptParse

  Parses the script for the given character
==============
*/
void AICast_ScriptParse( cast_state_t *cs )
{
	#define MAX_SCRIPT_EVENTS	64
	gentity_t	*ent;
	char		*pScript;
	char		*token;
	qboolean	wantName;
	qboolean	inScript;
	int			eventNum;
	cast_script_event_t	events[MAX_SCRIPT_EVENTS];
	int			numEventItems;
	cast_script_event_t *curEvent;
	char		params[MAX_QPATH];
	cast_script_stack_action_t	*action;
	int			i;
	int			bracketLevel;

	if (!level.scriptAI)
		return;

	ent = &g_entities[cs->entityNum];
	if (!ent->aiName)
		return;

	pScript = level.scriptAI;
	wantName = qtrue;
	inScript = qfalse;
	COM_BeginParseSession("AICast_ScriptParse");
	bracketLevel = 0;
	numEventItems = 0;

	memset( events, 0, sizeof(events) );

	while (1)
	{
		token = COM_Parse( &pScript );

		if ( !token[0] )
		{
			if ( !wantName )
			{
				G_Error( "AICast_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() );
			}
			break;
		}

		// end of script
		if ( token[0] == '}' )
		{
			if ( inScript )
			{
				break;
			}
			if ( wantName )
			{
				G_Error( "AICast_ScriptParse(), Error (line %d): '}' found, but not expected.\n", COM_GetCurrentParseLine() );
			}
			wantName = qtrue;
		}
		else if ( token[0] == '{' )
		{
			if ( wantName )
			{
				G_Error( "AICast_ScriptParse(), Error (line %d): '{' found, NAME expected.\n", COM_GetCurrentParseLine() );
			}
		}
		else if ( wantName )
		{
			if ( !Q_strcasecmp( ent->aiName, token ) )
			{
				inScript = qtrue;
				numEventItems = 0;
			}
			wantName = qfalse;
		}
		else if ( inScript )
		{
			if ( !Q_strcasecmp( token, "attributes" ) ) {
				// read in all the attributes
				AICast_CheckLevelAttributes( cs, ent, &pScript );
				continue;
			}
			eventNum = AICast_EventForString( token );
			if (eventNum < 0)
			{
				G_Error( "AICast_ScriptParse(), Error (line %d): unknown event: %s.\n", COM_GetCurrentParseLine(), token );
			}
			if (numEventItems >= MAX_SCRIPT_EVENTS)
			{
				G_Error( "AICast_ScriptParse(), Error (line %d): MAX_SCRIPT_EVENTS reached (%d)\n", COM_GetCurrentParseLine(), MAX_SCRIPT_EVENTS );
			}

			// if this is a "friendlysightcorpse" event, then disable corpse vis sharing
			if (!Q_stricmp(token, "friendlysightcorpse")) {
				cs->aiFlags &= ~AIFL_CORPSESIGHTING;
			}

			curEvent = &events[numEventItems];
			curEvent->eventNum = eventNum;
			memset( params, 0, sizeof(params) );

			// parse any event params before the start of this event's actions
			while ((token = COM_Parse( &pScript )) && (token[0] != '{'))
			{
				if (!token[0])
				{
					G_Error( "AICast_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() );
				}

				if (eventNum == 13) {	// statechange event, check params
					if (strlen( token ) > 1) {
						if (BG_IndexForString( token, animStateStr, qtrue ) < 0) {
							G_Error( "AICast_ScriptParse(), Error (line %d): unknown state type '%s'.\n", COM_GetCurrentParseLine(), token );
						}
					}
				}

				if (strlen( params ))	// add a space between each param
					Q_strcat( params, sizeof(params), " " );
				Q_strcat( params, sizeof(params), token );
			}

			if (strlen( params ))
			{	// copy the params into the event
				curEvent->params = G_Alloc( strlen( params ) + 1 );
				Q_strncpyz( curEvent->params, params, strlen(params)+1 );
			}

			// parse the actions for this event
			while ((token = COM_Parse( &pScript )) && (token[0] != '}'))
			{
				if (!token[0])
				{
					G_Error( "AICast_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() );
				}

				action = AICast_ActionForString( token );
				if (!action)
				{
					G_Error( "AICast_ScriptParse(), Error (line %d): unknown action: %s.\n", COM_GetCurrentParseLine(), token );
				}

				curEvent->stack.items[curEvent->stack.numItems].action = action;

				memset( params, 0, sizeof(params) );
				token = COM_ParseExt( &pScript, qfalse );
				for (i=0; token[0]; i++)
				{
					if (strlen( params )) {	// add a space between each param
						Q_strcat( params, sizeof(params), " " );
					}

					// Special case: playsound's need to be cached on startup to prevent in-game pauses
					if ((i==0) && !Q_stricmp(action->actionString, "playsound")) {
						G_SoundIndex(token);
					}

					if (strrchr(token,' '))	// need to wrap this param in quotes since it has more than one word
						Q_strcat( params, sizeof(params), "\"" );

					Q_strcat( params, sizeof(params), token );

					if (strrchr(token,' '))	// need to wrap this param in quotes since it has more than one word
						Q_strcat( params, sizeof(params), "\"" );

					token = COM_ParseExt( &pScript, qfalse );
				}

				if (strlen( params ))
				{	// copy the params into the event
					curEvent->stack.items[curEvent->stack.numItems].params = G_Alloc( strlen( params ) + 1 );
					Q_strncpyz( curEvent->stack.items[curEvent->stack.numItems].params, params, strlen(params)+1 );
				}

				curEvent->stack.numItems++;

				if (curEvent->stack.numItems >= AICAST_MAX_SCRIPT_STACK_ITEMS)
				{
					G_Error( "AICast_ScriptParse(): script exceeded MAX_SCRIPT_ITEMS (%d), line %d\n", AICAST_MAX_SCRIPT_STACK_ITEMS, COM_GetCurrentParseLine() );
				}
			}

			numEventItems++;
		}
		else	// skip this character completely
		{
      // TTimo gcc: suggest parentheses around assignment used as truth value
			while ( ( token = COM_Parse( &pScript ) ) )
			{
				if (!token[0]) {
					G_Error( "AICast_ScriptParse(), Error (line %d): '}' expected, end of script found.\n", COM_GetCurrentParseLine() );
				} else if (token[0] == '{')	{
					bracketLevel++;
				} else if (token[0] == '}') {
					if (!--bracketLevel)
						break;
				}
			}
		}
	}

	// alloc and copy the events into the cast_state_t for this cast
	if (numEventItems > 0)
	{
		cs->castScriptEvents = G_Alloc( sizeof(cast_script_event_t) * numEventItems );
		memcpy( cs->castScriptEvents, events, sizeof(cast_script_event_t) * numEventItems );
		cs->numCastScriptEvents = numEventItems;

		cs->castScriptStatus.castScriptEventIndex = -1;
	}
}

/*
================
AICast_ScriptChange
================
*/
void AICast_ScriptChange( cast_state_t *cs, int newScriptNum )
{
	cast_script_status_t scriptStatusBackup;

	cs->scriptCallIndex++;

	// backup the current scripting
	scriptStatusBackup = cs->castScriptStatus;

	// set the new script to this cast, and reset script status
	cs->castScriptStatus.castScriptStackHead = 0;
	cs->castScriptStatus.castScriptStackChangeTime = level.time;
	cs->castScriptStatus.castScriptEventIndex = newScriptNum;
	cs->castScriptStatus.scriptId = scriptStatusBackup.scriptId + 1;
	cs->castScriptStatus.scriptGotoId = -1;
	cs->castScriptStatus.scriptGotoEnt = -1;
	cs->castScriptStatus.scriptFlags |= SFL_FIRST_CALL;

	// try and run the script, if it doesn't finish, then abort the current script (discard backup)
	if (AICast_ScriptRun( cs, qtrue )) {
		// completed successfully
		cs->castScriptStatus.castScriptStackHead = scriptStatusBackup.castScriptStackHead;
		cs->castScriptStatus.castScriptStackChangeTime = scriptStatusBackup.castScriptStackChangeTime;
		cs->castScriptStatus.castScriptEventIndex = scriptStatusBackup.castScriptEventIndex;
		cs->castScriptStatus.scriptId = scriptStatusBackup.scriptId;
		cs->castScriptStatus.scriptFlags = scriptStatusBackup.scriptFlags;
	}
}

/*
================
AICast_ScriptEvent

  An event has occured, for which a script may exist
================
*/
void AICast_ScriptEvent( struct cast_state_s *cs, char *eventStr, char *params )
{
	int i, eventNum;

	eventNum = -1;

	// find out which event this is
	for (i=0; scriptEvents[i].eventStr; i++)
	{
		if (!Q_strcasecmp( eventStr, scriptEvents[i].eventStr ))
		{	// match found
			eventNum = i;
			break;
		}
	}

	if (eventNum < 0)
	{
		if (g_cheats.integer)	// dev mode
			G_Printf( "devmode-> AICast_ScriptEvent(), unknown event: %s\n", eventStr );
	}

	// show debugging info
	if (	(	(aicast_debug.integer == 1) ||
				(	(aicast_debug.integer == 2) &&
					((strlen(aicast_debugname.string) < 1) || (g_entities[cs->entityNum].aiName && !strcmp(aicast_debugname.string, g_entities[cs->entityNum].aiName)))))) {
		G_Printf( "(%s) AIScript event: %s %s ", g_entities[cs->entityNum].aiName, eventStr, params );
	}

	cs->aiFlags &= ~AIFL_DENYACTION;

	// see if this cast has this event
	for (i=0; i<cs->numCastScriptEvents; i++)
	{
		if (cs->castScriptEvents[i].eventNum == eventNum)
		{
			if (	(!cs->castScriptEvents[i].params)
				||	(!scriptEvents[eventNum].eventMatch || scriptEvents[eventNum].eventMatch( &cs->castScriptEvents[i], params ))) {

				// show debugging info
				if (	(	(aicast_debug.integer == 1) ||
							(	(aicast_debug.integer == 2) &&
								((strlen(aicast_debugname.string) < 1) || (g_entities[cs->entityNum].aiName && !strcmp(aicast_debugname.string, g_entities[cs->entityNum].aiName)))))) {
					G_Printf( "found, calling script\n", g_entities[cs->entityNum].aiName, eventStr, params );
				}

				AICast_ScriptChange( cs, i );
				break;
			}
		}
	}

	// show debugging info
	if (	(	(aicast_debug.integer == 1) ||
				(	(aicast_debug.integer == 2) &&
					((strlen(aicast_debugname.string) < 1) || (g_entities[cs->entityNum].aiName && !strcmp(aicast_debugname.string, g_entities[cs->entityNum].aiName)))))) {
		if (i==cs->numCastScriptEvents) {
			G_Printf( "not found\n" );
		}
	}

}

/*
================
AICast_ForceScriptEvent

  Definately run this event now, overriding any paised state
================
*/
void AICast_ForceScriptEvent( struct cast_state_s *cs, char *eventStr, char *params )
{
	int			oldPauseTime;

	oldPauseTime = cs->scriptPauseTime;
	cs->scriptPauseTime = 0;

	AICast_ScriptEvent( cs, eventStr, params );

	cs->scriptPauseTime = oldPauseTime;
}

/*
=============
AICast_ScriptRun

  returns qtrue if the script completed
=============
*/
qboolean AICast_ScriptRun( cast_state_t *cs, qboolean force )
{
	cast_script_stack_t *stack;

	if (saveGamePending)
		return qfalse;

	if (strlen( g_missionStats.string ) > 1 )
		return qfalse;

	if (!aicast_scripts.integer)
		return qtrue;

	if (cs->castScriptStatus.castScriptEventIndex < 0)
		return qtrue;

	if (!cs->castScriptEvents) {
		cs->castScriptStatus.castScriptEventIndex = -1;
		return qtrue;
	}

	if (!force && (cs->scriptPauseTime >= level.time)) {
		return qtrue;
	}
	
	stack = &cs->castScriptEvents[cs->castScriptStatus.castScriptEventIndex].stack;

	if (!stack->numItems) {
		cs->castScriptStatus.castScriptEventIndex = -1;
		return qtrue;
	}

	while (cs->castScriptStatus.castScriptStackHead < stack->numItems)
	{
		//
		// show debugging info
		if (	(cs->castScriptStatus.castScriptStackChangeTime == level.time) &&
				(	(aicast_debug.integer == 1) ||
					(	(aicast_debug.integer == 2) &&
						((strlen(aicast_debugname.string) < 1) || (g_entities[cs->entityNum].aiName && !strcmp(aicast_debugname.string, g_entities[cs->entityNum].aiName)))))) {
			G_Printf( "(%s) AIScript command: %s %s\n", g_entities[cs->entityNum].aiName, stack->items[cs->castScriptStatus.castScriptStackHead].action->actionString, (stack->items[cs->castScriptStatus.castScriptStackHead].params ? stack->items[cs->castScriptStatus.castScriptStackHead].params : "") );
		}
		//
		if (!stack->items[cs->castScriptStatus.castScriptStackHead].action->actionFunc( cs, stack->items[cs->castScriptStatus.castScriptStackHead].params )) {
			cs->castScriptStatus.scriptFlags &= ~SFL_FIRST_CALL;
			return qfalse;
		}
		// move to the next action in the script
		cs->castScriptStatus.castScriptStackHead++;
		// record the time that this new item became active
		cs->castScriptStatus.castScriptStackChangeTime = level.time;
		// reset misc stuff
		cs->castScriptStatus.scriptGotoId = -1;
		cs->castScriptStatus.scriptGotoEnt = -1;
		cs->castScriptStatus.scriptFlags |= SFL_FIRST_CALL;
	}

	cs->castScriptStatus.castScriptEventIndex = -1;

	return qtrue;
}
