
//**************************************************************************
//**
//** p_acs.c : Heretic 2 : Raven Software, Corp.
//**
//** $RCSfile: p_acs.c,v $
//** $Revision: 1.1 $
//** $Date: 1999/01/13 05:58:36 $
//** $Author: karl $
//**
//**************************************************************************

// HEADER FILES ------------------------------------------------------------

#include "h2def.h"
#include "p_local.h"

// MACROS ------------------------------------------------------------------

#define SCRIPT_CONTINUE 0
#define SCRIPT_STOP 1
#define SCRIPT_TERMINATE 2
#define OPEN_SCRIPTS_BASE 1000
#define PRINT_BUFFER_SIZE 256
#define GAME_SINGLE_PLAYER 0
#define GAME_NET_COOPERATIVE 1
#define GAME_NET_DEATHMATCH 2
#define TEXTURE_TOP 0
#define TEXTURE_MIDDLE 1
#define TEXTURE_BOTTOM 2
#define S_DROP ACScript->stackPtr--
#define S_POP ACScript->stack[--ACScript->stackPtr]
#define S_PUSH(x) ACScript->stack[ACScript->stackPtr++] = x

// TYPES -------------------------------------------------------------------

typedef struct
{
	int marker;
	int infoOffset;
	int code;
} acsHeader_t;

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

static void StartOpenACS(int number, int infoIndex, int *address);
static void ScriptFinished(int number);
static boolean TagBusy(int tag);
static boolean AddToACSStore(int map, int number, byte *args);
static int GetACSIndex(int number);
static void Push(int value);
static int Pop(void);
static int Top(void);
static void Drop(void);

static int CmdNOP(void);
static int CmdTerminate(void);
static int CmdSuspend(void);
static int CmdPushNumber(void);
static int CmdLSpec1(void);
static int CmdLSpec2(void);
static int CmdLSpec3(void);
static int CmdLSpec4(void);
static int CmdLSpec5(void);
static int CmdLSpec1Direct(void);
static int CmdLSpec2Direct(void);
static int CmdLSpec3Direct(void);
static int CmdLSpec4Direct(void);
static int CmdLSpec5Direct(void);
static int CmdAdd(void);
static int CmdSubtract(void);
static int CmdMultiply(void);
static int CmdDivide(void);
static int CmdModulus(void);
static int CmdEQ(void);
static int CmdNE(void);
static int CmdLT(void);
static int CmdGT(void);
static int CmdLE(void);
static int CmdGE(void);
static int CmdAssignScriptVar(void);
static int CmdAssignMapVar(void);
static int CmdAssignWorldVar(void);
static int CmdPushScriptVar(void);
static int CmdPushMapVar(void);
static int CmdPushWorldVar(void);
static int CmdAddScriptVar(void);
static int CmdAddMapVar(void);
static int CmdAddWorldVar(void);
static int CmdSubScriptVar(void);
static int CmdSubMapVar(void);
static int CmdSubWorldVar(void);
static int CmdMulScriptVar(void);
static int CmdMulMapVar(void);
static int CmdMulWorldVar(void);
static int CmdDivScriptVar(void);
static int CmdDivMapVar(void);
static int CmdDivWorldVar(void);
static int CmdModScriptVar(void);
static int CmdModMapVar(void);
static int CmdModWorldVar(void);
static int CmdIncScriptVar(void);
static int CmdIncMapVar(void);
static int CmdIncWorldVar(void);
static int CmdDecScriptVar(void);
static int CmdDecMapVar(void);
static int CmdDecWorldVar(void);
static int CmdGoto(void);
static int CmdIfGoto(void);
static int CmdDrop(void);
static int CmdDelay(void);
static int CmdDelayDirect(void);
static int CmdRandom(void);
static int CmdRandomDirect(void);
static int CmdThingCount(void);
static int CmdThingCountDirect(void);
static int CmdTagWait(void);
static int CmdTagWaitDirect(void);
static int CmdPolyWait(void);
static int CmdPolyWaitDirect(void);
static int CmdChangeFloor(void);
static int CmdChangeFloorDirect(void);
static int CmdChangeCeiling(void);
static int CmdChangeCeilingDirect(void);
static int CmdRestart(void);
static int CmdAndLogical(void);
static int CmdOrLogical(void);
static int CmdAndBitwise(void);
static int CmdOrBitwise(void);
static int CmdEorBitwise(void);
static int CmdNegateLogical(void);
static int CmdLShift(void);
static int CmdRShift(void);
static int CmdUnaryMinus(void);
static int CmdIfNotGoto(void);
static int CmdLineSide(void);
static int CmdScriptWait(void);
static int CmdScriptWaitDirect(void);
static int CmdClearLineSpecial(void);
static int CmdCaseGoto(void);
static int CmdBeginPrint(void);
static int CmdEndPrint(void);
static int CmdPrintString(void);
static int CmdPrintNumber(void);
static int CmdPrintCharacter(void);
static int CmdPlayerCount(void);
static int CmdGameType(void);
static int CmdGameSkill(void);
static int CmdTimer(void);
static int CmdSectorSound(void);
static int CmdAmbientSound(void);
static int CmdSoundSequence(void);
static int CmdSetLineTexture(void);
static int CmdSetLineBlocking(void);
static int CmdSetLineSpecial(void);
static int CmdThingSound(void);
static int CmdEndPrintBold(void);

static void ThingCount(int type, int tid);

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

// PUBLIC DATA DEFINITIONS -------------------------------------------------

int ACScriptCount;
byte *ActionCodeBase;
acsInfo_t *ACSInfo;
int MapVars[MAX_ACS_MAP_VARS];
int WorldVars[MAX_ACS_WORLD_VARS];
acsstore_t ACSStore[MAX_ACS_STORE+1]; // +1 for termination marker

// PRIVATE DATA DEFINITIONS ------------------------------------------------

static acs_t *ACScript;
static int *PCodePtr;
static byte SpecArgs[8];
static int ACStringCount;
static char **ACStrings;
static char PrintBuffer[PRINT_BUFFER_SIZE];
static acs_t *NewScript;

static int (*PCodeCmds[])(void) =
{
	CmdNOP,
	CmdTerminate,
	CmdSuspend,
	CmdPushNumber,
	CmdLSpec1,
	CmdLSpec2,
	CmdLSpec3,
	CmdLSpec4,
	CmdLSpec5,
	CmdLSpec1Direct,
	CmdLSpec2Direct,
	CmdLSpec3Direct,
	CmdLSpec4Direct,
	CmdLSpec5Direct,
	CmdAdd,
	CmdSubtract,
	CmdMultiply,
	CmdDivide,
	CmdModulus,
	CmdEQ,
	CmdNE,
	CmdLT,
	CmdGT,
	CmdLE,
	CmdGE,
	CmdAssignScriptVar,
	CmdAssignMapVar,
	CmdAssignWorldVar,
	CmdPushScriptVar,
	CmdPushMapVar,
	CmdPushWorldVar,
	CmdAddScriptVar,
	CmdAddMapVar,
	CmdAddWorldVar,
	CmdSubScriptVar,
	CmdSubMapVar,
	CmdSubWorldVar,
	CmdMulScriptVar,
	CmdMulMapVar,
	CmdMulWorldVar,
	CmdDivScriptVar,
	CmdDivMapVar,
	CmdDivWorldVar,
	CmdModScriptVar,
	CmdModMapVar,
	CmdModWorldVar,
	CmdIncScriptVar,
	CmdIncMapVar,
	CmdIncWorldVar,
	CmdDecScriptVar,
	CmdDecMapVar,
	CmdDecWorldVar,
	CmdGoto,
	CmdIfGoto,
	CmdDrop,
	CmdDelay,
	CmdDelayDirect,
	CmdRandom,
	CmdRandomDirect,
	CmdThingCount,
	CmdThingCountDirect,
	CmdTagWait,
	CmdTagWaitDirect,
	CmdPolyWait,
	CmdPolyWaitDirect,
	CmdChangeFloor,
	CmdChangeFloorDirect,
	CmdChangeCeiling,
	CmdChangeCeilingDirect,
	CmdRestart,
	CmdAndLogical,
	CmdOrLogical,
	CmdAndBitwise,
	CmdOrBitwise,
	CmdEorBitwise,
	CmdNegateLogical,
	CmdLShift,
	CmdRShift,
	CmdUnaryMinus,
	CmdIfNotGoto,
	CmdLineSide,
	CmdScriptWait,
	CmdScriptWaitDirect,
	CmdClearLineSpecial,
	CmdCaseGoto,
	CmdBeginPrint,
	CmdEndPrint,
	CmdPrintString,
	CmdPrintNumber,
	CmdPrintCharacter,
	CmdPlayerCount,
	CmdGameType,
	CmdGameSkill,
	CmdTimer,
	CmdSectorSound,
	CmdAmbientSound,
	CmdSoundSequence,
	CmdSetLineTexture,
	CmdSetLineBlocking,
	CmdSetLineSpecial,
	CmdThingSound,
	CmdEndPrintBold
};

// CODE --------------------------------------------------------------------

//==========================================================================
//
// P_LoadACScripts
//
//==========================================================================

void P_LoadACScripts(int lump)
{
	int i;
	int *buffer;
	acsHeader_t *header;
	acsInfo_t *info;

	header = W_CacheLumpNum(lump, PU_LEVEL);
	ActionCodeBase = (byte *)header;
	buffer = (int *)((byte *)header+header->infoOffset);
	ACScriptCount = *buffer++;
	if(ACScriptCount == 0)
	{ // Empty behavior lump
		return;
	}
	ACSInfo = Z_Malloc(ACScriptCount*sizeof(acsInfo_t), PU_LEVEL, 0);
	memset(ACSInfo, 0, ACScriptCount*sizeof(acsInfo_t));
	for(i = 0, info = ACSInfo; i < ACScriptCount; i++, info++)
	{
		info->number = *buffer++;
		info->address = (int *)((byte *)ActionCodeBase+*buffer++);
		info->argCount = *buffer++;
		if(info->number >= OPEN_SCRIPTS_BASE)
		{ // Auto-activate
			info->number -= OPEN_SCRIPTS_BASE;
			StartOpenACS(info->number, i, info->address);
			info->state = ASTE_RUNNING;
		}
		else
		{
			info->state = ASTE_INACTIVE;
		}
	}
	ACStringCount = *buffer++;
	ACStrings = (char **)buffer;
	for(i = 0; i < ACStringCount; i++)
	{
		ACStrings[i] += (int)ActionCodeBase;
	}
	memset(MapVars, 0, sizeof(MapVars));
}

//==========================================================================
//
// StartOpenACS
//
//==========================================================================

static void StartOpenACS(int number, int infoIndex, int *address)
{
	acs_t *script;

	script = Z_Malloc(sizeof(acs_t), PU_LEVSPEC, 0);
	memset(script, 0, sizeof(acs_t));
	script->number = number;

	// World objects are allotted 1 second for initialization
	script->delayCount = 35;

	script->infoIndex = infoIndex;
	script->ip = address;
	script->thinker.function = T_InterpretACS;
	P_AddThinker(&script->thinker);
}

//==========================================================================
//
// P_CheckACSStore
//
// Scans the ACS store and executes all scripts belonging to the current
// map.
//
//==========================================================================

void P_CheckACSStore(void)
{
	acsstore_t *store;

	for(store = ACSStore; store->map != 0; store++)
	{
		if(store->map == gamemap)
		{
			P_StartACS(store->script, 0, store->args, NULL, NULL, 0);
			if(NewScript)
			{
				NewScript->delayCount = 35;
			}
			store->map = -1;
		}
	}
}

//==========================================================================
//
// P_StartACS
//
//==========================================================================

static char ErrorMsg[128];

boolean P_StartACS(int number, int map, byte *args, mobj_t *activator,
	line_t *line, int side)
{
	int i;
	acs_t *script;
	int infoIndex;
	aste_t *statePtr;

	NewScript = NULL;
	if(map && map != gamemap)
	{ // Add to the script store
		return AddToACSStore(map, number, args);
	}
	infoIndex = GetACSIndex(number);
	if(infoIndex == -1)
	{ // Script not found
		//I_Error("P_StartACS: Unknown script number %d", number);
		sprintf(ErrorMsg, "P_STARTACS ERROR: UNKNOWN SCRIPT %d", number);
		P_SetMessage(&players[consoleplayer], ErrorMsg, true);
	}
	statePtr = &ACSInfo[infoIndex].state;
	if(*statePtr == ASTE_SUSPENDED)
	{ // Resume a suspended script
		*statePtr = ASTE_RUNNING;
		return true;
	}
	if(*statePtr != ASTE_INACTIVE)
	{ // Script is already executing
		return false;
	}
	script = Z_Malloc(sizeof(acs_t), PU_LEVSPEC, 0);
	memset(script, 0, sizeof(acs_t));
	script->number = number;
	script->infoIndex = infoIndex;
	script->activator = activator;
	script->line = line;
	script->side = side;
	script->ip = ACSInfo[infoIndex].address;
	script->thinker.function = T_InterpretACS;
	for(i = 0; i < ACSInfo[infoIndex].argCount; i++)
	{
		script->vars[i] = args[i];
	}
	*statePtr = ASTE_RUNNING;
	P_AddThinker(&script->thinker);
	NewScript = script;
	return true;
}

//==========================================================================
//
// AddToACSStore
//
//==========================================================================

static boolean AddToACSStore(int map, int number, byte *args)
{
	int i;
	int index;

	index = -1;
	for(i = 0; ACSStore[i].map != 0; i++)
	{
		if(ACSStore[i].script == number
		&& ACSStore[i].map == map)
		{ // Don't allow duplicates
			return false;
		}
		if(index == -1 && ACSStore[i].map == -1)
		{ // Remember first empty slot
			index = i;
		}
	}
	if(index == -1)
	{ // Append required
		if(i == MAX_ACS_STORE)
		{
			I_Error("AddToACSStore: MAX_ACS_STORE (%d) exceeded.",
				MAX_ACS_STORE);
		}
		index = i;
		ACSStore[index+1].map = 0;
	}
	ACSStore[index].map = map;
	ACSStore[index].script = number;
	*((int *)ACSStore[index].args) = *((int *)args);
	return true;
}

//==========================================================================
//
// P_StartLockedACS
//
//==========================================================================


boolean P_StartLockedACS(line_t *line, byte *args, mobj_t *mo, int side)
{
	int i;
	int lock;
	byte newArgs[5];
	char LockedBuffer[80];

	extern char *TextKeyMessages[11];

	lock = args[4];
	if(!mo->player)
	{
		return false;
	}
	if(lock)
	{
		if(!(mo->player->keys&(1<<(lock-1))))
		{
			sprintf(LockedBuffer, "YOU NEED THE %s\n", 
				TextKeyMessages[lock-1]);
			P_SetMessage(mo->player, LockedBuffer, true);
			S_StartSound(mo, SFX_DOOR_LOCKED);
			return false;
		}
	}
	for(i = 0; i < 4; i++)
	{
		newArgs[i] = args[i];
	}
	newArgs[4] = 0;
	return P_StartACS(newArgs[0], newArgs[1], &newArgs[2], mo,
		line, side);
}

//==========================================================================
//
// P_TerminateACS
//
//==========================================================================

boolean P_TerminateACS(int number, int map)
{
	int infoIndex;

	infoIndex = GetACSIndex(number);
	if(infoIndex == -1)
	{ // Script not found
		return false;
	}
	if(ACSInfo[infoIndex].state == ASTE_INACTIVE
		|| ACSInfo[infoIndex].state == ASTE_TERMINATING)
	{ // States that disallow termination
		return false;
	}
	ACSInfo[infoIndex].state = ASTE_TERMINATING;
	return true;
}

//==========================================================================
//
// P_SuspendACS
//
//==========================================================================

boolean P_SuspendACS(int number, int map)
{
	int infoIndex;

	infoIndex = GetACSIndex(number);
	if(infoIndex == -1)
	{ // Script not found
		return false;
	}
	if(ACSInfo[infoIndex].state == ASTE_INACTIVE
		|| ACSInfo[infoIndex].state == ASTE_SUSPENDED
		|| ACSInfo[infoIndex].state == ASTE_TERMINATING)
	{ // States that disallow suspension
		return false;
	}
	ACSInfo[infoIndex].state = ASTE_SUSPENDED;
	return true;
}

//==========================================================================
//
// P_Init
//
//==========================================================================

void P_ACSInitNewGame(void)
{
	memset(WorldVars, 0, sizeof(WorldVars));
	memset(ACSStore, 0, sizeof(ACSStore));
}

//==========================================================================
//
// T_InterpretACS
//
//==========================================================================

void T_InterpretACS(acs_t *script)
{
	int cmd;
	int action;

	if(ACSInfo[script->infoIndex].state == ASTE_TERMINATING)
	{
		ACSInfo[script->infoIndex].state = ASTE_INACTIVE;
		ScriptFinished(ACScript->number);
		P_RemoveThinker(&ACScript->thinker);
		return;
	}
	if(ACSInfo[script->infoIndex].state != ASTE_RUNNING)
	{
		return;
	}
	if(script->delayCount)
	{
		script->delayCount--;
		return;
	}
	ACScript = script;
	PCodePtr = ACScript->ip;
	do
	{
		cmd = *PCodePtr++;
		action = PCodeCmds[cmd]();
	} while(action == SCRIPT_CONTINUE);
	ACScript->ip = PCodePtr;
	if(action == SCRIPT_TERMINATE)
	{
		ACSInfo[script->infoIndex].state = ASTE_INACTIVE;
		ScriptFinished(ACScript->number);
		P_RemoveThinker(&ACScript->thinker);
	}
}

//==========================================================================
//
// P_TagFinished
//
//==========================================================================

void P_TagFinished(int tag)
{
	int i;

	if(TagBusy(tag) == true)
	{
		return;
	}
	for(i = 0; i < ACScriptCount; i++)
	{
		if(ACSInfo[i].state == ASTE_WAITINGFORTAG
			&& ACSInfo[i].waitValue == tag)
		{
			ACSInfo[i].state = ASTE_RUNNING;
		}
	}
}

//==========================================================================
//
// P_PolyobjFinished
//
//==========================================================================

void P_PolyobjFinished(int po)
{
	int i;

	if(PO_Busy(po) == true)
	{
		return;
	}
	for(i = 0; i < ACScriptCount; i++)
	{
		if(ACSInfo[i].state == ASTE_WAITINGFORPOLY
			&& ACSInfo[i].waitValue == po)
		{
			ACSInfo[i].state = ASTE_RUNNING;
		}
	}
}

//==========================================================================
//
// ScriptFinished
//
//==========================================================================

static void ScriptFinished(int number)
{
	int i;

	for(i = 0; i < ACScriptCount; i++)
	{
		if(ACSInfo[i].state == ASTE_WAITINGFORSCRIPT
			&& ACSInfo[i].waitValue == number)
		{
			ACSInfo[i].state = ASTE_RUNNING;
		}
	}
}

//==========================================================================
//
// TagBusy
//
//==========================================================================

static boolean TagBusy(int tag)
{
	int sectorIndex;

	sectorIndex = -1;
	while((sectorIndex = P_FindSectorFromTag(tag, sectorIndex)) >= 0)
	{
		if(sectors[sectorIndex].specialdata)
		{
			return true;
		}
	}
	return false;
}

//==========================================================================
//
// GetACSIndex
//
// Returns the index of a script number.  Returns -1 if the script number
// is not found.
//
//==========================================================================

static int GetACSIndex(int number)
{
	int i;

	for(i = 0; i < ACScriptCount; i++)
	{
		if(ACSInfo[i].number == number)
		{
			return i;
		}
	}
	return -1;
}

//==========================================================================
//
// Push
//
//==========================================================================

static void Push(int value)
{
	ACScript->stack[ACScript->stackPtr++] = value;
}

//==========================================================================
//
// Pop
//
//==========================================================================

static int Pop(void)
{
	return ACScript->stack[--ACScript->stackPtr];
}

//==========================================================================
//
// Top
//
//==========================================================================

static int Top(void)
{
	return ACScript->stack[ACScript->stackPtr-1];
}

//==========================================================================
//
// Drop
//
//==========================================================================

static void Drop(void)
{
	ACScript->stackPtr--;
}

//==========================================================================
//
// P-Code Commands
//
//==========================================================================

static int CmdNOP(void)
{
	return SCRIPT_CONTINUE;
}

static int CmdTerminate(void)
{
	return SCRIPT_TERMINATE;
}

static int CmdSuspend(void)
{
	ACSInfo[ACScript->infoIndex].state = ASTE_SUSPENDED;
	return SCRIPT_STOP;
}

static int CmdPushNumber(void)
{
	Push(*PCodePtr++);
	return SCRIPT_CONTINUE;
}

static int CmdLSpec1(void)
{
	int special;

	special = *PCodePtr++;
	SpecArgs[0] = Pop();
	P_ExecuteLineSpecial(special, SpecArgs, ACScript->line,
		ACScript->side, ACScript->activator);
	return SCRIPT_CONTINUE;
}

static int CmdLSpec2(void)
{
	int special;

	special = *PCodePtr++;
	SpecArgs[1] = Pop();
	SpecArgs[0] = Pop();
	P_ExecuteLineSpecial(special, SpecArgs, ACScript->line,
		ACScript->side, ACScript->activator);
	return SCRIPT_CONTINUE;
}

static int CmdLSpec3(void)
{
	int special;

	special = *PCodePtr++;
	SpecArgs[2] = Pop();
	SpecArgs[1] = Pop();
	SpecArgs[0] = Pop();
	P_ExecuteLineSpecial(special, SpecArgs, ACScript->line,
		ACScript->side, ACScript->activator);
	return SCRIPT_CONTINUE;
}

static int CmdLSpec4(void)
{
	int special;

	special = *PCodePtr++;
	SpecArgs[3] = Pop();
	SpecArgs[2] = Pop();
	SpecArgs[1] = Pop();
	SpecArgs[0] = Pop();
	P_ExecuteLineSpecial(special, SpecArgs, ACScript->line,
		ACScript->side, ACScript->activator);
	return SCRIPT_CONTINUE;
}

static int CmdLSpec5(void)
{
	int special;

	special = *PCodePtr++;
	SpecArgs[4] = Pop();
	SpecArgs[3] = Pop();
	SpecArgs[2] = Pop();
	SpecArgs[1] = Pop();
	SpecArgs[0] = Pop();
	P_ExecuteLineSpecial(special, SpecArgs, ACScript->line,
		ACScript->side, ACScript->activator);
	return SCRIPT_CONTINUE;
}

static int CmdLSpec1Direct(void)
{
	int special;

	special = *PCodePtr++;
	SpecArgs[0] = *PCodePtr++;
	P_ExecuteLineSpecial(special, SpecArgs, ACScript->line,
		ACScript->side, ACScript->activator);
	return SCRIPT_CONTINUE;
}

static int CmdLSpec2Direct(void)
{
	int special;

	special = *PCodePtr++;
	SpecArgs[0] = *PCodePtr++;
	SpecArgs[1] = *PCodePtr++;
	P_ExecuteLineSpecial(special, SpecArgs, ACScript->line,
		ACScript->side, ACScript->activator);
	return SCRIPT_CONTINUE;
}

static int CmdLSpec3Direct(void)
{
	int special;

	special = *PCodePtr++;
	SpecArgs[0] = *PCodePtr++;
	SpecArgs[1] = *PCodePtr++;
	SpecArgs[2] = *PCodePtr++;
	P_ExecuteLineSpecial(special, SpecArgs, ACScript->line,
		ACScript->side, ACScript->activator);
	return SCRIPT_CONTINUE;
}

static int CmdLSpec4Direct(void)
{
	int special;

	special = *PCodePtr++;
	SpecArgs[0] = *PCodePtr++;
	SpecArgs[1] = *PCodePtr++;
	SpecArgs[2] = *PCodePtr++;
	SpecArgs[3] = *PCodePtr++;
	P_ExecuteLineSpecial(special, SpecArgs, ACScript->line,
		ACScript->side, ACScript->activator);
	return SCRIPT_CONTINUE;
}

static int CmdLSpec5Direct(void)
{
	int special;

	special = *PCodePtr++;
	SpecArgs[0] = *PCodePtr++;
	SpecArgs[1] = *PCodePtr++;
	SpecArgs[2] = *PCodePtr++;
	SpecArgs[3] = *PCodePtr++;
	SpecArgs[4] = *PCodePtr++;
	P_ExecuteLineSpecial(special, SpecArgs, ACScript->line,
		ACScript->side, ACScript->activator);
	return SCRIPT_CONTINUE;
}

static int CmdAdd(void)
{
	Push(Pop()+Pop());
	return SCRIPT_CONTINUE;
}

static int CmdSubtract(void)
{
	int operand2;

	operand2 = Pop();
	Push(Pop()-operand2);
	return SCRIPT_CONTINUE;
}

static int CmdMultiply(void)
{
	Push(Pop()*Pop());
	return SCRIPT_CONTINUE;
}

static int CmdDivide(void)
{
	int operand2;

	operand2 = Pop();
	Push(Pop()/operand2);
	return SCRIPT_CONTINUE;
}

static int CmdModulus(void)
{
	int operand2;

	operand2 = Pop();
	Push(Pop()%operand2);
	return SCRIPT_CONTINUE;
}

static int CmdEQ(void)
{
	Push(Pop() == Pop());
	return SCRIPT_CONTINUE;
}

static int CmdNE(void)
{
	Push(Pop() != Pop());
	return SCRIPT_CONTINUE;
}

static int CmdLT(void)
{
	int operand2;

	operand2 = Pop();
	Push(Pop() < operand2);
	return SCRIPT_CONTINUE;
}

static int CmdGT(void)
{
	int operand2;

	operand2 = Pop();
	Push(Pop() > operand2);
	return SCRIPT_CONTINUE;
}

static int CmdLE(void)
{
	int operand2;

	operand2 = Pop();
	Push(Pop() <= operand2);
	return SCRIPT_CONTINUE;
}

static int CmdGE(void)
{
	int operand2;

	operand2 = Pop();
	Push(Pop() >= operand2);
	return SCRIPT_CONTINUE;
}

static int CmdAssignScriptVar(void)
{
	ACScript->vars[*PCodePtr++] = Pop();
	return SCRIPT_CONTINUE;
}

static int CmdAssignMapVar(void)
{
	MapVars[*PCodePtr++] = Pop();
	return SCRIPT_CONTINUE;
}

static int CmdAssignWorldVar(void)
{
	WorldVars[*PCodePtr++] = Pop();
	return SCRIPT_CONTINUE;
}

static int CmdPushScriptVar(void)
{
	Push(ACScript->vars[*PCodePtr++]);
	return SCRIPT_CONTINUE;
}

static int CmdPushMapVar(void)
{
	Push(MapVars[*PCodePtr++]);
	return SCRIPT_CONTINUE;
}

static int CmdPushWorldVar(void)
{
	Push(WorldVars[*PCodePtr++]);
	return SCRIPT_CONTINUE;
}

static int CmdAddScriptVar(void)
{
	ACScript->vars[*PCodePtr++] += Pop();
	return SCRIPT_CONTINUE;
}

static int CmdAddMapVar(void)
{
	MapVars[*PCodePtr++] += Pop();
	return SCRIPT_CONTINUE;
}

static int CmdAddWorldVar(void)
{
	WorldVars[*PCodePtr++] += Pop();
	return SCRIPT_CONTINUE;
}

static int CmdSubScriptVar(void)
{
	ACScript->vars[*PCodePtr++] -= Pop();
	return SCRIPT_CONTINUE;
}

static int CmdSubMapVar(void)
{
	MapVars[*PCodePtr++] -= Pop();
	return SCRIPT_CONTINUE;
}

static int CmdSubWorldVar(void)
{
	WorldVars[*PCodePtr++] -= Pop();
	return SCRIPT_CONTINUE;
}

static int CmdMulScriptVar(void)
{
	ACScript->vars[*PCodePtr++] *= Pop();
	return SCRIPT_CONTINUE;
}

static int CmdMulMapVar(void)
{
	MapVars[*PCodePtr++] *= Pop();
	return SCRIPT_CONTINUE;
}

static int CmdMulWorldVar(void)
{
	WorldVars[*PCodePtr++] *= Pop();
	return SCRIPT_CONTINUE;
}

static int CmdDivScriptVar(void)
{
	ACScript->vars[*PCodePtr++] /= Pop();
	return SCRIPT_CONTINUE;
}

static int CmdDivMapVar(void)
{
	MapVars[*PCodePtr++] /= Pop();
	return SCRIPT_CONTINUE;
}

static int CmdDivWorldVar(void)
{
	WorldVars[*PCodePtr++] /= Pop();
	return SCRIPT_CONTINUE;
}

static int CmdModScriptVar(void)
{
	ACScript->vars[*PCodePtr++] %= Pop();
	return SCRIPT_CONTINUE;
}

static int CmdModMapVar(void)
{
	MapVars[*PCodePtr++] %= Pop();
	return SCRIPT_CONTINUE;
}

static int CmdModWorldVar(void)
{
	WorldVars[*PCodePtr++] %= Pop();
	return SCRIPT_CONTINUE;
}

static int CmdIncScriptVar(void)
{
	ACScript->vars[*PCodePtr++]++;
	return SCRIPT_CONTINUE;
}

static int CmdIncMapVar(void)
{
	MapVars[*PCodePtr++]++;
	return SCRIPT_CONTINUE;
}

static int CmdIncWorldVar(void)
{
	WorldVars[*PCodePtr++]++;
	return SCRIPT_CONTINUE;
}

static int CmdDecScriptVar(void)
{
	ACScript->vars[*PCodePtr++]--;
	return SCRIPT_CONTINUE;
}

static int CmdDecMapVar(void)
{
	MapVars[*PCodePtr++]--;
	return SCRIPT_CONTINUE;
}

static int CmdDecWorldVar(void)
{
	WorldVars[*PCodePtr++]--;
	return SCRIPT_CONTINUE;
}

static int CmdGoto(void)
{
	PCodePtr = (int *)(ActionCodeBase+*PCodePtr);
	return SCRIPT_CONTINUE;
}

static int CmdIfGoto(void)
{
	if(Pop())
	{
		PCodePtr = (int *)(ActionCodeBase+*PCodePtr);
	}
	else
	{
		PCodePtr++;
	}
	return SCRIPT_CONTINUE;
}

static int CmdDrop(void)
{
	Drop();
	return SCRIPT_CONTINUE;
}

static int CmdDelay(void)
{
	ACScript->delayCount = Pop();
	return SCRIPT_STOP;
}

static int CmdDelayDirect(void)
{
	ACScript->delayCount = *PCodePtr++;
	return SCRIPT_STOP;
}

static int CmdRandom(void)
{
	int low;
	int high;

	high = Pop();
	low = Pop();
	Push(low+(P_Random()%(high-low+1)));
	return SCRIPT_CONTINUE;
}

static int CmdRandomDirect(void)
{
	int low;
	int high;

	low = *PCodePtr++;
	high = *PCodePtr++;
	Push(low+(P_Random()%(high-low+1)));
	return SCRIPT_CONTINUE;
}

static int CmdThingCount(void)
{
	int tid;

	tid = Pop();
	ThingCount(Pop(), tid);
	return SCRIPT_CONTINUE;
}

static int CmdThingCountDirect(void)
{
	int type;

	type = *PCodePtr++;
	ThingCount(type, *PCodePtr++);
	return SCRIPT_CONTINUE;
}

static void ThingCount(int type, int tid)
{
	int count;
	int searcher;
	mobj_t *mobj;
	mobjtype_t moType;
	thinker_t *think;

	if(!(type+tid))
	{ // Nothing to count
		return;
	}
	moType = TranslateThingType[type];
	count = 0;
	searcher = -1;
	if(tid)
	{ // Count TID things
		while((mobj = P_FindMobjFromTID(tid, &searcher)) != NULL)
		{
			if(type == 0)
			{ // Just count TIDs
				count++;
			}
			else if(moType == mobj->type)
			{
				if(mobj->flags&MF_COUNTKILL && mobj->health <= 0)
				{ // Don't count dead monsters
					continue;
				}
				count++;
			}
		}
	}
	else
	{ // Count only types
		for(think = thinkercap.next; think != &thinkercap;
			think = think->next)
		{
			if(think->function != P_MobjThinker)
			{ // Not a mobj thinker
				continue;
			}
			mobj = (mobj_t *)think;
			if(mobj->type != moType)
			{ // Doesn't match
				continue;
			}
			if(mobj->flags&MF_COUNTKILL && mobj->health <= 0)
			{ // Don't count dead monsters
				continue;
			}
			count++;
		}
	}
	Push(count);
}

static int CmdTagWait(void)
{
	ACSInfo[ACScript->infoIndex].waitValue = Pop();
	ACSInfo[ACScript->infoIndex].state = ASTE_WAITINGFORTAG;
	return SCRIPT_STOP;
}

static int CmdTagWaitDirect(void)
{
	ACSInfo[ACScript->infoIndex].waitValue = *PCodePtr++;
	ACSInfo[ACScript->infoIndex].state = ASTE_WAITINGFORTAG;
	return SCRIPT_STOP;
}

static int CmdPolyWait(void)
{
	ACSInfo[ACScript->infoIndex].waitValue = Pop();
	ACSInfo[ACScript->infoIndex].state = ASTE_WAITINGFORPOLY;
	return SCRIPT_STOP;
}

static int CmdPolyWaitDirect(void)
{
	ACSInfo[ACScript->infoIndex].waitValue = *PCodePtr++;
	ACSInfo[ACScript->infoIndex].state = ASTE_WAITINGFORPOLY;
	return SCRIPT_STOP;
}

static int CmdChangeFloor(void)
{
	int tag;
	int flat;
	int sectorIndex;

	flat = R_FlatNumForName(ACStrings[Pop()]);
	tag = Pop();
	sectorIndex = -1;
	while((sectorIndex = P_FindSectorFromTag(tag, sectorIndex)) >= 0)
	{
		sectors[sectorIndex].floorpic = flat;
	}
	return SCRIPT_CONTINUE;
}

static int CmdChangeFloorDirect(void)
{
	int tag;
	int flat;
	int sectorIndex;

	tag = *PCodePtr++;
	flat = R_FlatNumForName(ACStrings[*PCodePtr++]);
	sectorIndex = -1;
	while((sectorIndex = P_FindSectorFromTag(tag, sectorIndex)) >= 0)
	{
		sectors[sectorIndex].floorpic = flat;
	}
	return SCRIPT_CONTINUE;
}

static int CmdChangeCeiling(void)
{
	int tag;
	int flat;
	int sectorIndex;

	flat = R_FlatNumForName(ACStrings[Pop()]);
	tag = Pop();
	sectorIndex = -1;
	while((sectorIndex = P_FindSectorFromTag(tag, sectorIndex)) >= 0)
	{
		sectors[sectorIndex].ceilingpic = flat;
	}
	return SCRIPT_CONTINUE;
}

static int CmdChangeCeilingDirect(void)
{
	int tag;
	int flat;
	int sectorIndex;

	tag = *PCodePtr++;
	flat = R_FlatNumForName(ACStrings[*PCodePtr++]);
	sectorIndex = -1;
	while((sectorIndex = P_FindSectorFromTag(tag, sectorIndex)) >= 0)
	{
		sectors[sectorIndex].ceilingpic = flat;
	}
	return SCRIPT_CONTINUE;
}

static int CmdRestart(void)
{
	PCodePtr = ACSInfo[ACScript->infoIndex].address;
	return SCRIPT_CONTINUE;
}

static int CmdAndLogical(void)
{
	Push(Pop() && Pop());
	return SCRIPT_CONTINUE;
}

static int CmdOrLogical(void)
{
	Push(Pop() || Pop());
	return SCRIPT_CONTINUE;
}

static int CmdAndBitwise(void)
{
	Push(Pop()&Pop());
	return SCRIPT_CONTINUE;
}

static int CmdOrBitwise(void)
{
	Push(Pop()|Pop());
	return SCRIPT_CONTINUE;
}

static int CmdEorBitwise(void)
{
	Push(Pop()^Pop());
	return SCRIPT_CONTINUE;
}

static int CmdNegateLogical(void)
{
	Push(!Pop());
	return SCRIPT_CONTINUE;
}

static int CmdLShift(void)
{
	int operand2;

	operand2 = Pop();
	Push(Pop()<<operand2);
	return SCRIPT_CONTINUE;
}

static int CmdRShift(void)
{
	int operand2;

	operand2 = Pop();
	Push(Pop()>>operand2);
	return SCRIPT_CONTINUE;
}

static int CmdUnaryMinus(void)
{
	Push(-Pop());
	return SCRIPT_CONTINUE;
}

static int CmdIfNotGoto(void)
{
	if(Pop())
	{
		PCodePtr++;
	}
	else
	{
		PCodePtr = (int *)(ActionCodeBase+*PCodePtr);
	}
	return SCRIPT_CONTINUE;
}

static int CmdLineSide(void)
{
	Push(ACScript->side);
	return SCRIPT_CONTINUE;
}

static int CmdScriptWait(void)
{
	ACSInfo[ACScript->infoIndex].waitValue = Pop();
	ACSInfo[ACScript->infoIndex].state = ASTE_WAITINGFORSCRIPT;
	return SCRIPT_STOP;
}

static int CmdScriptWaitDirect(void)
{
	ACSInfo[ACScript->infoIndex].waitValue = *PCodePtr++;
	ACSInfo[ACScript->infoIndex].state = ASTE_WAITINGFORSCRIPT;
	return SCRIPT_STOP;
}

static int CmdClearLineSpecial(void)
{
	if(ACScript->line)
	{
		ACScript->line->special = 0;
	}
	return SCRIPT_CONTINUE;
}

static int CmdCaseGoto(void)
{
	if(Top() == *PCodePtr++)
	{
		PCodePtr = (int *)(ActionCodeBase+*PCodePtr);
		Drop();
	}
	else
	{
		PCodePtr++;
	}
	return SCRIPT_CONTINUE;
}

static int CmdBeginPrint(void)
{
	*PrintBuffer = 0;
	return SCRIPT_CONTINUE;
}

static int CmdEndPrint(void)
{
	player_t *player;

	if(ACScript->activator && ACScript->activator->player)
	{
		player = ACScript->activator->player;
	}
	else
	{
		player = &players[consoleplayer];
	}
	P_SetMessage(player, PrintBuffer, true);
	return SCRIPT_CONTINUE;
}

static int CmdEndPrintBold(void)
{
	int i;

	for(i = 0; i < MAXPLAYERS; i++)
	{
		if(playeringame[i])
		{
			P_SetYellowMessage(&players[i], PrintBuffer, true);
		}
	}
	return SCRIPT_CONTINUE;
}

static int CmdPrintString(void)
{
	strcat(PrintBuffer, ACStrings[Pop()]);
	return SCRIPT_CONTINUE;
}

static int CmdPrintNumber(void)
{
	char tempStr[16];

	sprintf(tempStr, "%d", Pop());
	strcat(PrintBuffer, tempStr);
	return SCRIPT_CONTINUE;
}

static int CmdPrintCharacter(void)
{
	char *bufferEnd;

	bufferEnd = PrintBuffer+strlen(PrintBuffer);
	*bufferEnd++ = Pop();
	*bufferEnd = 0;
	return SCRIPT_CONTINUE;
}

static int CmdPlayerCount(void)
{
	int i;
	int count;

	count = 0;
	for(i = 0; i < MAXPLAYERS; i++)
	{
		count += playeringame[i];
	}
	Push(count);
	return SCRIPT_CONTINUE;
}

static int CmdGameType(void)
{
	int gametype;

	if(netgame == false)
	{
		gametype = GAME_SINGLE_PLAYER;
	}
	else if(deathmatch)
	{
		gametype = GAME_NET_DEATHMATCH;
	}
	else
	{
		gametype = GAME_NET_COOPERATIVE;
	}
	Push(gametype);
	return SCRIPT_CONTINUE;
}

static int CmdGameSkill(void)
{
	Push(gameskill);
	return SCRIPT_CONTINUE;
}

static int CmdTimer(void)
{
	Push(leveltime);
	return SCRIPT_CONTINUE;
}

static int CmdSectorSound(void)
{
	int volume;
	mobj_t *mobj;

	mobj = NULL;
	if(ACScript->line)
	{
		mobj = (mobj_t *)&ACScript->line->frontsector->soundorg;
	}
	volume = Pop();
	S_StartSoundAtVolume(mobj, S_GetSoundID(ACStrings[Pop()]), volume);
	return SCRIPT_CONTINUE;
}

static int CmdThingSound(void)
{
	int tid;
	int sound;
	int volume;
	mobj_t *mobj;
	int searcher;

	volume = Pop();
	sound = S_GetSoundID(ACStrings[Pop()]);
	tid = Pop();
	searcher = -1;
	while((mobj = P_FindMobjFromTID(tid, &searcher)) != NULL)
	{
		S_StartSoundAtVolume(mobj, sound, volume);
	}
	return SCRIPT_CONTINUE;
}

static int CmdAmbientSound(void)
{
	int volume;

	volume = Pop();
	S_StartSoundAtVolume(NULL, S_GetSoundID(ACStrings[Pop()]), volume);
	return SCRIPT_CONTINUE;
}

static int CmdSoundSequence(void)
{
	mobj_t *mobj;

	mobj = NULL;
	if(ACScript->line)
	{
		mobj = (mobj_t *)&ACScript->line->frontsector->soundorg;
	}
	SN_StartSequenceName(mobj, ACStrings[Pop()]);
	return SCRIPT_CONTINUE;
}

static int CmdSetLineTexture(void)
{
	line_t *line;
	int lineTag;
	int side;
	int position;
	int texture;
	int searcher;

	texture = R_TextureNumForName(ACStrings[Pop()]);
	position = Pop();
	side = Pop();
	lineTag = Pop();
	searcher = -1;
	while((line = P_FindLine(lineTag, &searcher)) != NULL)
	{
		if(position == TEXTURE_MIDDLE)
		{
			sides[line->sidenum[side]].midtexture = texture;
		}
		else if(position == TEXTURE_BOTTOM)
		{
			sides[line->sidenum[side]].bottomtexture = texture;
		}
		else
		{ // TEXTURE_TOP
			sides[line->sidenum[side]].toptexture = texture;
		}
	}
	return SCRIPT_CONTINUE;
}

static int CmdSetLineBlocking(void)
{
	line_t *line;
	int lineTag;
	boolean blocking;
	int searcher;

	blocking = Pop() ? ML_BLOCKING : 0;
	lineTag = Pop();
	searcher = -1;
	while((line = P_FindLine(lineTag, &searcher)) != NULL)
	{
		line->flags = (line->flags&~ML_BLOCKING)|blocking;
	}
	return SCRIPT_CONTINUE;
}

static int CmdSetLineSpecial(void)
{
	line_t *line;
	int lineTag;
	int special, arg1, arg2, arg3, arg4, arg5;
	int searcher;

	arg5 = Pop();
	arg4 = Pop();
	arg3 = Pop();
	arg2 = Pop();
	arg1 = Pop();
	special = Pop();
	lineTag = Pop();
	searcher = -1;
	while((line = P_FindLine(lineTag, &searcher)) != NULL)
	{
		line->special = special;
		line->arg1 = arg1;
		line->arg2 = arg2;
		line->arg3 = arg3;
		line->arg4 = arg4;
		line->arg5 = arg5;
	}
	return SCRIPT_CONTINUE;
}
