/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-11-0/engines/cine/script.cpp $
 * $Id: script.cpp 30459 2008-01-12 23:07:51Z sev $
 *
 */


#include "common/endian.h"

#include "cine/cine.h"
#include "cine/bg_list.h"
#include "cine/object.h"
#include "cine/sound.h"
#include "cine/various.h"

namespace Cine {

prcLinkedListStruct *_currentScriptElement;
byte *_currentScriptPtr;
uint16 _currentScriptParams;
uint16 _currentPosition;
uint16 _currentLine;
uint16 _closeScript;

struct Opcode {
	void (*proc)();
	const char *args;
};

const Opcode *_opcodeTable;
int _numOpcodes;

void setupOpcodes() {
	static const Opcode opcodeTableFW[] = {
		/* 00 */
		{ o1_modifyObjectParam, "bbw" },
		{ o1_getObjectParam, "bbb" },
		{ o1_addObjectParam, "bbw" },
		{ o1_subObjectParam, "bbw" },
		/* 04 */
		{ o1_add2ObjectParam, "bbw" },
		{ o1_sub2ObjectParam, "bbw" },
		{ o1_compareObjectParam, "bbw" },
		{ o1_setupObject, "bwwww" },
		/* 08 */
		{ o1_checkCollision, "bwwww" },
		{ o1_loadVar, "bc" },
		{ o1_addVar, "bc" },
		{ o1_subVar, "bc" },
		/* 0C */
		{ o1_mulVar, "bc" },
		{ o1_divVar, "bc" },
		{ o1_compareVar, "bc" },
		{ o1_modifyObjectParam2, "bbb" },
		/* 10 */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ o1_loadMask0, "b" },
		/* 14 */
		{ o1_unloadMask0, "b" },
		{ o1_addToBgList, "b" },
		{ o1_loadMask1, "b" },
		{ o1_unloadMask1, "b" },
		/* 18 */
		{ o1_loadMask4, "b" },
		{ o1_unloadMask4, "b" },
		{ o1_addSpriteFilledToBgList, "b" },
		{ o1_op1B, "" },
		/* 1C */
		{ 0, 0 },
		{ o1_label, "l" },
		{ o1_goto, "b" },
		{ o1_gotoIfSup, "b" },
		/* 20 */
		{ o1_gotoIfSupEqu, "b" },
		{ o1_gotoIfInf, "b" },
		{ o1_gotoIfInfEqu, "b" },
		{ o1_gotoIfEqu, "b" },
		/* 24 */
		{ o1_gotoIfDiff, "b" },
		{ o1_removeLabel, "b" },
		{ o1_loop, "bb" },
		{ 0, 0 },
		/* 28 */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		/* 2C */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		/* 30 */
		{ 0, 0 },
		{ o1_startGlobalScript, "b" },
		{ o1_endGlobalScript, "b" },
		{ 0, 0 },
		/* 34 */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		/* 38 */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ o1_loadAnim, "s" },
		/* 3C */
		{ o1_loadBg, "s" },
		{ o1_loadCt, "s" },
		{ 0, 0 },
		{ o1_loadPart, "s" },
		/* 40 */
		{ o1_closePart, "" },
		{ o1_loadNewPrcName, "bs" },
		{ o1_requestCheckPendingDataLoad, "" },
		{ 0, 0 },
		/* 44 */
		{ 0, 0 },
		{ o1_blitAndFade, "" },
		{ o1_fadeToBlack, "" },
		{ o1_transformPaletteRange, "bbwww" },
		/* 48 */
		{ 0, 0 },
		{ o1_setDefaultMenuColor2, "b" },
		{ o1_palRotate, "bbb" },
		{ 0, 0 },
		/* 4C */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ o1_break, "" },
		/* 50 */
		{ o1_endScript, "x" },
		{ o1_message, "bwwww" },
		{ o1_loadGlobalVar, "bc" },
		{ o1_compareGlobalVar, "bc" },
		/* 54 */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		/* 58 */
		{ 0, 0 },
		{ o1_declareFunctionName, "s" },
		{ o1_freePartRange, "bb" },
		{ o1_unloadAllMasks, "" },
		// 5C */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		/* 60 */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ o1_setScreenDimensions, "wwww" },
		/* 64 */
		{ o1_displayBackground, "" },
		{ o1_initializeZoneData, "" },
		{ o1_setZoneDataEntry, "bw" },
		{ o1_getZoneDataEntry, "bb" },
		/* 68 */
		{ o1_setDefaultMenuColor, "b" },
		{ o1_allowPlayerInput, "" },
		{ o1_disallowPlayerInput, "" },
		{ o1_changeDataDisk, "b" },
		/* 6C */
		{ 0, 0 },
		{ o1_loadMusic, "s" },
		{ o1_playMusic, "" },
		{ o1_fadeOutMusic, "" },
		/* 70 */
		{ o1_stopSample, "" },
		{ o1_op71, "bw" },
		{ o1_op72, "wbw" },
		{ o1_op73, "wbw" },
		/* 74 */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ o1_playSample, "bbwbww" },
		/* 78 */
		{ o1_playSample, "bbwbww" },
		{ o1_disableSystemMenu, "b" },
		{ o1_loadMask5, "b" },
		{ o1_unloadMask5, "b" }
	};

	// TODO: We need to verify the Operation Stealth opcodes.

	static const Opcode opcodeTableOS[] = {
		/* 00 */
		{ o1_modifyObjectParam, "bbw" },
		{ o1_getObjectParam, "bbb" },
		{ o1_addObjectParam, "bbw" },
		{ o1_subObjectParam, "bbw" },
		/* 04 */
		{ o1_add2ObjectParam, "bbw" },
		{ o1_sub2ObjectParam, "bbw" },
		{ o1_compareObjectParam, "bbw" },
		{ o1_setupObject, "bwwww" },
		/* 08 */
		{ o1_checkCollision, "bwwww" },
		{ o1_loadVar, "bc" },
		{ o1_addVar, "bc" },
		{ o1_subVar, "bc" },
		/* 0C */
		{ o1_mulVar, "bc" },
		{ o1_divVar, "bc" },
		{ o1_compareVar, "bc" },
		{ o1_modifyObjectParam2, "bbb" },
		/* 10 */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ o1_loadMask0, "b" },
		/* 14 */
		{ o1_unloadMask0, "b" },
		{ o1_addToBgList, "b" },
		{ o1_loadMask1, "b" },
		{ o1_unloadMask1, "b" },
		/* 18 */
		{ o1_loadMask4, "b" },
		{ o1_unloadMask4, "b" },
		{ o1_addSpriteFilledToBgList, "b" },
		{ o1_op1B, "" },
		/* 1C */
		{ 0, 0 },
		{ o1_label, "l" },
		{ o1_goto, "b" },
		{ o1_gotoIfSup, "b" },
		/* 20 */
		{ o1_gotoIfSupEqu, "b" },
		{ o1_gotoIfInf, "b" },
		{ o1_gotoIfInfEqu, "b" },
		{ o1_gotoIfEqu, "b" },
		/* 24 */
		{ o1_gotoIfDiff, "b" },
		{ o1_removeLabel, "b" },
		{ o1_loop, "bb" },
		{ 0, 0 },
		/* 28 */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		/* 2C */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		/* 30 */
		{ 0, 0 },
		{ o1_startGlobalScript, "b" },
		{ o1_endGlobalScript, "b" },
		{ 0, 0 },
		/* 34 */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		/* 38 */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ o1_loadAnim, "s" },
		/* 3C */
		{ o1_loadBg, "s" },
		{ o1_loadCt, "s" },
		{ 0, 0 },
		{ o2_loadPart, "s" },
		/* 40 */
		{ 0, 0 }, /* o1_closePart, triggered by some scripts (STARTA.PRC 4 for ex.) */
		{ o1_loadNewPrcName, "bs" },
		{ o1_requestCheckPendingDataLoad, "" },
		{ 0, 0 },
		/* 44 */
		{ 0, 0 },
		{ o1_blitAndFade, "" },
		{ o1_fadeToBlack, "" },
		{ o1_transformPaletteRange, "bbwww" },
		/* 48 */
		{ 0, 0 },
		{ o1_setDefaultMenuColor2, "b" },
		{ o1_palRotate, "bbb" },
		{ 0, 0 },
		/* 4C */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ o1_break, "" },
		/* 50 */
		{ o1_endScript, "x" },
		{ o1_message, "bwwww" },
		{ o1_loadGlobalVar, "bc" },
		{ o1_compareGlobalVar, "bc" },
		/* 54 */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		/* 58 */
		{ 0, 0 },
		{ o1_declareFunctionName, "s" },
		{ o1_freePartRange, "bb" },
		{ o1_unloadAllMasks, "" },
		// 5C */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		/* 60 */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ o1_setScreenDimensions, "wwww" },
		/* 64 */
		{ o1_displayBackground, "" },
		{ o1_initializeZoneData, "" },
		{ o1_setZoneDataEntry, "bw" },
		{ o1_getZoneDataEntry, "bb" },
		/* 68 */
		{ o1_setDefaultMenuColor, "b" },
		{ o1_allowPlayerInput, "" },
		{ o1_disallowPlayerInput, "" },
		{ o1_changeDataDisk, "b" },
		/* 6C */
		{ 0, 0 },
		{ o1_loadMusic, "s" },
		{ o1_playMusic, "" },
		{ o1_fadeOutMusic, "" },
		/* 70 */
		{ o1_stopSample, "" },
		{ o1_op71, "bw" },
		{ o1_op72, "wbw" },
		{ o1_op72, "wbw" },
		/* 74 */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ o2_playSample, "bbwbww" },
		/* 78 */
		{ o2_playSampleAlt, "bbwbww" },
		{ o1_disableSystemMenu, "b" },
		{ o1_loadMask5, "b" },
		{ o1_unloadMask5, "b" },
		/* 7C */
		{ 0, 0 },
		{ 0, 0 },
		{ 0, 0 },
		{ o2_addSeqListElement, "bbbbwww" },
		/* 80 */
		{ o2_removeSeq, "bb" },
		{ o2_op81, "" },
		{ o2_op82, "bbw" },
		{ o2_isSeqRunning, "bb" },
		/* 84 */
		{ o2_gotoIfSupNearest, "b" },
		{ o2_gotoIfSupEquNearest, "b" },
		{ o2_gotoIfInfNearest, "b" },
		{ o2_gotoIfInfEquNearest, "b" },
		/* 88 */
		{ o2_gotoIfEquNearest, "b" },
		{ o2_gotoIfDiffNearest, "b" },
		{ 0, 0 },
		{ o2_startObjectScript, "b" },
		/* 8C */
		{ o2_stopObjectScript, "b" },
		{ o2_op8D, "wwwwwwww" },
		{ o2_addBackground, "bs" },
		{ o2_removeBackground, "b" },
		/* 90 */
		{ o2_loadAbs, "bs" },
		{ o2_loadBg, "b" },
		{ 0, 0 },
		{ 0, 0 },
		/* 94 */
		{ 0, 0 },
		{ o1_changeDataDisk, "b" },
		{ 0, 0 },
		{ 0, 0 },
		/* 98 */
		{ 0, 0 },
		{ 0, 0 },
		{ o2_wasZoneChecked, "" },
		{ o2_op9B, "wwwwwwww" },
		/* 9C */
		{ o2_op9C, "wwww" },
		{ o2_useBgScroll, "b" },
		{ o2_setAdditionalBgVScroll, "c" },
		{ o2_op9F, "ww" },
		/* A0 */
		{ o2_addGfxElementA0, "ww" },
		{ o2_opA1, "ww" },
		{ o2_opA2, "ww" },
		{ o2_opA3, "ww" },
		/* A4 */
		{ o2_loadMask22, "b" },
		{ o2_unloadMask22, "b" },
		{ 0, 0 },
		{ 0, 0 },
		/* A8 */
		{ 0, 0 },
		{ o1_changeDataDisk, "b" }
	};

	if (g_cine->getGameType() == Cine::GType_FW) {
		_opcodeTable = opcodeTableFW;
		_numOpcodes = ARRAYSIZE(opcodeTableFW);
	} else {
		_opcodeTable = opcodeTableOS;
		_numOpcodes = ARRAYSIZE(opcodeTableOS);
	}
}

byte getNextByte() {
	byte val = *(_currentScriptPtr + _currentPosition);
	_currentPosition++;
	return val;
}

uint16 getNextWord() {
	uint16 val = READ_BE_UINT16(_currentScriptPtr + _currentPosition);
	_currentPosition += 2;
	return val;
}

const char *getNextString() {
	const char *val = (const char *)(_currentScriptPtr + _currentPosition);
	_currentPosition += strlen(val) + 1;
	return val;
}

void addGfxElementA0(int16 param1, int16 param2) {
	overlayHeadElement *currentHead = &overlayHead;
	overlayHeadElement *tempHead = currentHead;
	overlayHeadElement *newElement;

	currentHead = tempHead->next;

	while (currentHead) {
		if (objectTable[currentHead->objIdx].mask == objectTable[param1].mask) {
			if (currentHead->type == 2 || currentHead->objIdx == 3) {
				break;
			}
		}

		tempHead = currentHead;
		currentHead = currentHead->next;
	}

	if (currentHead && currentHead->objIdx == param1 && currentHead->type == 20 && currentHead->x == param2)
		return;

	newElement = new overlayHeadElement;

	newElement->next = tempHead->next;
	tempHead->next = newElement;

	newElement->objIdx = param1;
	newElement->type = 20;

	newElement->x = param2;
	newElement->y = 0;
	newElement->width = 0;
	newElement->color = 0;

	if (!currentHead)
		currentHead = &overlayHead;

	newElement->previous = currentHead->previous;

	currentHead->previous = newElement;
}

void removeSeq(uint16 param1, uint16 param2, uint16 param3) {
	SeqListElement *currentHead = &seqList;
	SeqListElement *tempHead = currentHead;

	while (currentHead && (currentHead->var6 != param1 || currentHead->var4 != param2 || currentHead->varE != param3)) {
		tempHead = currentHead;
		currentHead = tempHead->next;
	}

	if (currentHead && currentHead->var6 == param1 && currentHead->var4 == param2 && currentHead->varE == param3) {
		currentHead->var4 = -1;
	}
}

uint16 isSeqRunning(uint16 param1, uint16 param2, uint16 param3) {
	SeqListElement *currentHead = &seqList;
	SeqListElement *tempHead = currentHead;

	while (currentHead && (currentHead->var6 != param1 || currentHead->var4 != param2 || currentHead->varE != param3)) {
		tempHead = currentHead;
		currentHead = tempHead->next;
	}

	if (currentHead && currentHead->var6 == param1 && currentHead->var4 == param2 && currentHead->varE == param3) {
		return 1;
	}

	return 0;
}

ScriptStruct scriptTable[NUM_MAX_SCRIPT];

void stopGlobalScript(uint16 scriptIdx) {
	prcLinkedListStruct *currentHead = &globalScriptsHead;
	prcLinkedListStruct *tempHead = currentHead;

	currentHead = tempHead->next;

	while (currentHead && (currentHead->scriptIdx != scriptIdx)) {
		tempHead = currentHead;
		currentHead = tempHead->next;
	}

	if (!currentHead) {
		return;
	}

	if (currentHead->scriptIdx != scriptIdx) {
		return;
	}

	currentHead->scriptIdx = -1;
}

uint16 computeScriptStackSub(bool computeAllLabels, byte *scriptPtr, int16 *stackPtr, uint16 scriptSize, byte labelIndex, uint16 startOffset) {
	uint16 position;

	if (computeAllLabels) {
		for (int i = 0; i < SCRIPT_STACK_SIZE; i++) {
			stackPtr[i] = -1;
		}
		position = 0;
	} else {
		position = startOffset;
	}
	while (position < scriptSize) {
		uint8 opcode = scriptPtr[position];
		position++;
		if (opcode == 0 || opcode > _numOpcodes) {
			continue;
		}
		if (!_opcodeTable[opcode - 1].args) {
			warning("Undefined opcode 0x%02X in computeScriptStackSub", opcode - 1);
			continue;
		}
		for (const char *p = _opcodeTable[opcode - 1].args; *p; ++p) {
			switch (*p) {
			case 'b': // byte
				position++;
				break;
			case 'w': // word
				position += 2;
				break;
			case 'c': { // byte != 0 ? byte : word
					uint8 test = scriptPtr[position];
					position++;
					if (test) {
						position++;
					} else {
						position += 2;
					}
				}
				break;
			case 'l': { // label
					uint8 index = scriptPtr[position];
					position++;
					if (computeAllLabels) {
						stackPtr[index] = position;
					} else {
						if (labelIndex == index) {
							return position;
						}
					}
				}
				break;
			case 's': // string
				while (scriptPtr[position++] != 0);
				break;
			case 'x': // exit script
				return position;
			}
		}
	}
	return position;
}

void computeScriptStack(byte *scriptPtr, int16 *stackPtr, uint16 scriptSize) {
	computeScriptStackSub(true, scriptPtr, stackPtr, scriptSize, 0, 0);
}

uint16 computeScriptStackFromScript(byte *scriptPtr, uint16 currentPosition, uint16 labelIdx, uint16 scriptSize) {
	return computeScriptStackSub(false, scriptPtr, (int16 *)&dummyU16, (uint16)scriptSize, labelIdx, currentPosition);
}

void palRotate(byte a, byte b, byte c) {
	if (c == 1) {
		uint16 currentColor = c_palette[b];

		for (int16 i = b; i > a; i--) {
			c_palette[i] = c_palette[i - 1];
		}

		c_palette[a] = currentColor;
	}
}

void addScriptToList0(uint16 idx) {
	uint16 i;
	prcLinkedListStruct *pNewElement;
	prcLinkedListStruct *currentHead = &globalScriptsHead;
	prcLinkedListStruct *tempHead = currentHead;

	assert(idx <= NUM_MAX_SCRIPT);

	currentHead = tempHead->next;

	while (currentHead) {
		tempHead = currentHead;

		assert(tempHead);

		currentHead = tempHead->next;
	}

	pNewElement = new prcLinkedListStruct;

	assert(pNewElement);

	pNewElement->next = tempHead->next;
	tempHead->next = pNewElement;

	// copy the stack into the script instance
	for (i = 0; i < SCRIPT_STACK_SIZE; i++) {
		pNewElement->stack[i] = scriptTable[idx].stack[i];
	}

	for (i = 0; i < 50; i++) {
		pNewElement->localVars[i] = 0;
	}

	pNewElement->compareResult = 0;
	pNewElement->scriptPosition = 0;

	pNewElement->scriptPtr = scriptTable[idx].ptr;
	pNewElement->scriptIdx = idx;
}

int16 endScript0(uint16 scriptIdx) {
	prcLinkedListStruct *currentHead = &globalScriptsHead;
	prcLinkedListStruct *tempHead = currentHead;

	//assert(scriptIdx <= NUM_MAX_SCRIPT);

	currentHead = tempHead->next;

	while (currentHead && currentHead->scriptIdx != scriptIdx) {
		tempHead = currentHead;
		currentHead = tempHead->next;
	}

	if (!currentHead) {
		return -1;
	}

	if (currentHead->scriptIdx != scriptIdx) {
		return -1;
	}

	currentHead->scriptIdx = -1;

	return 0;
}

int16 endScript1(uint16 scriptIdx) {
	prcLinkedListStruct *currentHead = &objScriptList;
	prcLinkedListStruct *tempHead = currentHead;

	currentHead = tempHead->next;

	while (currentHead && currentHead->scriptIdx != scriptIdx) {
		tempHead = currentHead;
		currentHead = tempHead->next;
	}

	if (!currentHead) {
		return -1;
	}

	if (currentHead->scriptIdx != scriptIdx) {
		return -1;
	}

	currentHead->scriptIdx = -1;

	return 0;
}

int16 getZoneFromPosition(byte *page, int16 x, int16 y, int16 width) {
	byte *ptr = page + (y * width) + x / 2;
	byte zoneVar;

	if (!(x % 2)) {
		zoneVar = (*(ptr) >> 4) & 0xF;
	} else {
		zoneVar = (*(ptr)) & 0xF;
	}

	return zoneVar;
}

int16 getZoneFromPositionRaw(byte *page, int16 x, int16 y, int16 width) {
	byte *ptr = page + (y * width) + x;
	byte zoneVar;

	zoneVar = (*(ptr)) & 0xF;

	return zoneVar;
}

int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneIdx) {
	int16 lx = objectTable[objIdx].x + x;
	int16 ly = objectTable[objIdx].y + y;
	int16 idx;

	for (int16 i = 0; i < numZones; i++) {
		idx = getZoneFromPositionRaw(page3Raw, lx + i, ly, 320);

		assert(idx >= 0 && idx <= NUM_MAX_ZONE);

		if (zoneData[idx] == zoneIdx) {
			return 1;
		}
	}

	return 0;
}

uint16 compareVars(int16 a, int16 b) {
	uint16 flag = 0;

	if (a == b) {
		flag |= kCmpEQ;
	} else if (a > b) {
		flag |= kCmpGT;
	} else if (a < b) {
		flag |= kCmpLT;
	}

	return flag;
}

// ------------------------------------------------------------------------
// FUTURE WARS opcodes
// ------------------------------------------------------------------------

void o1_modifyObjectParam() {
	byte objIdx = getNextByte();
	byte paramIdx = getNextByte();
	int16 newValue = getNextWord();

	debugC(5, kCineDebugScript, "Line: %d: modifyObjectParam(objIdx:%d,paramIdx:%d,newValue:%d)", _currentLine, objIdx, paramIdx, newValue);

	modifyObjectParam(objIdx, paramIdx, newValue);
}

void o1_getObjectParam() {
	byte objIdx = getNextByte();
	byte paramIdx = getNextByte();
	byte newValue = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: getObjectParam(objIdx:%d,paramIdx:%d,var:%d)", _currentLine, objIdx, paramIdx, newValue);

	_currentScriptElement->localVars[newValue] = getObjectParam(objIdx, paramIdx);
}

void o1_addObjectParam() {
	byte objIdx = getNextByte();
	byte paramIdx = getNextByte();
	int16 newValue = getNextWord();

	debugC(5, kCineDebugScript, "Line: %d: addObjectParam(objIdx:%d,paramIdx:%d,newValue:%d)", _currentLine, objIdx, paramIdx, newValue);

	addObjectParam(objIdx, paramIdx, newValue);
}

void o1_subObjectParam() {
	byte objIdx = getNextByte();
	byte paramIdx = getNextByte();
	int16 newValue = getNextWord();

	debugC(5, kCineDebugScript, "Line: %d: subObjectParam(objIdx:%d,paramIdx:%d,newValue:%d)", _currentLine, objIdx, paramIdx, newValue);

	subObjectParam(objIdx, paramIdx, newValue);
}

void o1_add2ObjectParam() {
	getNextByte();
	getNextByte();
	getNextWord();
	warning("STUB: o1_add2ObjectParam()");
}

void o1_sub2ObjectParam() {
	getNextByte();
	getNextByte();
	getNextWord();
	warning("STUB: o1_sub2ObjectParam()");
}

void o1_compareObjectParam() {
	byte objIdx = getNextByte();
	byte param1 = getNextByte();
	int16 param2 = getNextWord();

	debugC(5, kCineDebugScript, "Line: %d: compareObjectParam(objIdx:%d,type:%d,value:%d)", _currentLine, objIdx, param1, param2);

	_currentScriptElement->compareResult = compareObjectParam(objIdx, param1, param2);
}

void o1_setupObject() {
	byte objIdx = getNextByte();
	int16 param1 = getNextWord();
	int16 param2 = getNextWord();
	int16 param3 = getNextWord();
	int16 param4 = getNextWord();

	debugC(5, kCineDebugScript, "Line: %d: setupObject(objIdx:%d,%d,%d,%d,%d)", _currentLine, objIdx, param1, param2, param3, param4);

	setupObject(objIdx, param1, param2, param3, param4);
}

void o1_checkCollision() {
	byte objIdx = getNextByte();
	int16 param1 = getNextWord();
	int16 param2 = getNextWord();
	int16 param3 = getNextWord();
	int16 param4 = getNextWord();

	debugC(5, kCineDebugScript, "Line: %d: checkCollision(objIdx:%d,%d,%d,%d,%d)", _currentLine, objIdx, param1, param2, param3, param4);

	_currentScriptElement->compareResult = checkCollision(objIdx, param1, param2, param3, param4);
}

void o1_loadVar() {
	byte varIdx = getNextByte();
	byte varType = getNextByte();

	if (varType) {
		byte dataIdx = getNextByte();
		int16 var;

		switch (varType) {
		case 1:
			debugC(5, kCineDebugScript, "Line: %d: var[%d] = var[%d]", _currentLine, varIdx, dataIdx);
			_currentScriptElement->localVars[varIdx] = _currentScriptElement->localVars[dataIdx];
			break;
		case 2:
			debugC(5, kCineDebugScript, "Line: %d: var[%d] = globalVars[%d]", _currentLine, varIdx, dataIdx);
			_currentScriptElement->localVars[varIdx] = globalVars[dataIdx];
			break;
		case 3:
			debugC(5, kCineDebugScript, "Line: %d: var[%d] = mouseX", _currentLine, varIdx);
			getMouseData(mouseUpdateStatus, &dummyU16, (uint16 *)&var, (uint16 *)&dummyU16);
			_currentScriptElement->localVars[varIdx] = var;
			break;
		case 4:
			debugC(5, kCineDebugScript, "Line: %d: var[%d] = mouseY", _currentLine, varIdx);
			getMouseData(mouseUpdateStatus, &dummyU16, (uint16 *)&dummyU16, (uint16 *)&var);
			_currentScriptElement->localVars[varIdx] = var;
			break;
		case 5:
			debugC(5, kCineDebugScript, "Line: %d: var[%d] = rand mod %d", _currentLine, varIdx, dataIdx);
			_currentScriptElement->localVars[varIdx] = g_cine->_rnd.getRandomNumber(dataIdx - 1);
			break;
		case 8:
			debugC(5, kCineDebugScript, "Line: %d: var[%d] = file[%d].packedSize", _currentLine, varIdx, dataIdx);
			_currentScriptElement->localVars[varIdx] = partBuffer[dataIdx].packedSize;
			break;
		case 9:
			debugC(5, kCineDebugScript, "Line: %d: var[%d] = file[%d].unpackedSize", _currentLine, varIdx, dataIdx);
			_currentScriptElement->localVars[varIdx] = partBuffer[dataIdx].unpackedSize;
			break;
		default:
			error("executeScript: o1_loadVar: Unknown variable type %d", varType);
		}
	} else {
		int16 value = getNextWord();

		debugC(5, kCineDebugScript, "Line: %d: var[%d] = %d", _currentLine, varIdx, value);
		_currentScriptElement->localVars[varIdx] = value;
	}
}

void o1_addVar() {
	byte varIdx = getNextByte();
	byte varType = getNextByte();

	if (varType) {
		byte dataIdx = getNextByte();

		debugC(5, kCineDebugScript, "Line: %d: var[%d] += var[%d]", _currentLine, varIdx, dataIdx);
		_currentScriptElement->localVars[varIdx] += _currentScriptElement->localVars[dataIdx];
	} else {
		int16 value = getNextWord();

		debugC(5, kCineDebugScript, "Line: %d: var[%d] += %d", _currentLine, varIdx, value);
		_currentScriptElement->localVars[varIdx] += value;
	}
}

void o1_subVar() {
	byte varIdx = getNextByte();
	byte varType = getNextByte();

	if (varType) {
		byte dataIdx = getNextByte();

		debugC(5, kCineDebugScript, "Line: %d: var[%d] -= var[%d]", _currentLine, varIdx, dataIdx);
		_currentScriptElement->localVars[varIdx] -= _currentScriptElement->localVars[dataIdx];
	} else {
		int16 value = getNextWord();

		debugC(5, kCineDebugScript, "Line: %d: var[%d] -= %d", _currentLine, varIdx, value);
		_currentScriptElement->localVars[varIdx] -= value;
	}
}

void o1_mulVar() {
	byte varIdx = getNextByte();
	byte varType = getNextByte();

	if (varType) {
		byte dataIdx = getNextByte();

		debugC(5, kCineDebugScript, "Line: %d: var[%d] *= var[%d]", _currentLine, varIdx, dataIdx);
		_currentScriptElement->localVars[varIdx] *= _currentScriptElement->localVars[dataIdx];
	} else {
		int16 value = getNextWord();

		debugC(5, kCineDebugScript, "Line: %d: var[%d] *= %d", _currentLine, varIdx, value);
		_currentScriptElement->localVars[varIdx] *= value;
	}
}

void o1_divVar() {
	byte varIdx = getNextByte();
	byte varType = getNextByte();

	if (varType) {
		byte dataIdx = getNextByte();

		debugC(5, kCineDebugScript, "Line: %d: var[%d] /= var[%d]", _currentLine, varIdx, dataIdx);
		_currentScriptElement->localVars[varIdx] /= _currentScriptElement->localVars[dataIdx];
	} else {
		int16 value = getNextWord();

		debugC(5, kCineDebugScript, "Line: %d: var[%d] /= %d", _currentLine, varIdx, value);
		_currentScriptElement->localVars[varIdx] /= value;
	}
}

void o1_compareVar() {
	byte varIdx = getNextByte();
	byte varType = getNextByte();

	if (varType) {
		byte dataIdx = getNextByte();

		// printf("Val: %d\n", dataIdx);

		if (varType == 1) {
			assert(varIdx < 50);
			assert(dataIdx < 50);

			debugC(5, kCineDebugScript, "Line: %d: compare var[%d] and var[%d]", _currentLine, varIdx, dataIdx);
			_currentScriptElement->compareResult = compareVars(_currentScriptElement->localVars[varIdx], _currentScriptElement->localVars[dataIdx]);
		} else if (varType == 2) {
			assert(varIdx < 50);

			debugC(5, kCineDebugScript, "Line: %d: compare var[%d] and globalVar[%d]", _currentLine, varIdx, dataIdx);
			_currentScriptElement->compareResult = compareVars(_currentScriptElement->localVars[varIdx], globalVars[dataIdx]);
		}
	} else {
		int16 value = getNextWord();

		debugC(5, kCineDebugScript, "Line: %d: compare var[%d] and %d", _currentLine, varIdx, value);
		_currentScriptElement->compareResult = compareVars(_currentScriptElement->localVars[varIdx], value);
	}
}

void o1_modifyObjectParam2() {
	byte objIdx = getNextByte();
	byte paramIdx = getNextByte();
	byte newValue = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: modifyObjectParam2(objIdx:%d,paramIdx:%d,var[%d])", _currentLine, objIdx, paramIdx, newValue);

	modifyObjectParam(objIdx, paramIdx, _currentScriptElement->localVars[newValue]);
}

void o1_loadMask0() {
	// OP_loadV7Element
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: addSpriteOverlay(%d)", _currentLine, param);
	loadOverlayElement(param, 0);
}

void o1_unloadMask0() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: removeSpriteOverlay(%d)", _currentLine, param);
	freeOverlay(param, 0);
}

void o1_addToBgList() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: addToBGList(%d)", _currentLine, param);
	addToBGList(param);
}

void o1_loadMask1() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: addOverlay1(%d)", _currentLine, param);
	loadOverlayElement(param, 1);
}

void o1_unloadMask1() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: removeOverlay1(%d)", _currentLine, param);
	freeOverlay(param, 1);
}

void o1_loadMask4() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: addOverlayType4(%d)", _currentLine, param);
	loadOverlayElement(param, 4);
}

void o1_unloadMask4() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: removeSpriteOverlay4(%d)", _currentLine, param);
	freeOverlay(param, 4);
}

void o1_addSpriteFilledToBgList() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: op1A(%d) -> TODO !", _currentLine, param);
	addSpriteFilledToBGList(param);
}

void o1_op1B() {
	debugC(5, kCineDebugScript, "Line: %d: freeBgIncrustList", _currentLine);
	freeBgIncrustList();
}

void o1_label() {
	byte labelIdx = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: label(%d)", _currentLine, labelIdx);
	_currentScriptElement->stack[labelIdx] = _currentPosition;
}

void o1_goto() {
	byte labelIdx = getNextByte();

	assert(_currentScriptElement->stack[labelIdx] != -1);

	debugC(5, kCineDebugScript, "Line: %d: goto label(%d)", _currentLine, labelIdx);
	_currentPosition = _currentScriptElement->stack[labelIdx];
}

void o1_gotoIfSup() {
	byte labelIdx = getNextByte();

	if (_currentScriptElement->compareResult == kCmpGT) {
		assert(_currentScriptElement->stack[labelIdx] != -1);

		debugC(5, kCineDebugScript, "Line: %d: if(>) goto %d (true)", _currentLine, labelIdx);
		_currentPosition = _currentScriptElement->stack[labelIdx];
	} else {
		debugC(5, kCineDebugScript, "Line: %d: if(>) goto %d (false)", _currentLine, labelIdx);
	}
}

void o1_gotoIfSupEqu() {
	byte labelIdx = getNextByte();

	if (_currentScriptElement->compareResult & (kCmpGT | kCmpEQ)) {
		assert(_currentScriptElement->stack[labelIdx] != -1);

		debugC(5, kCineDebugScript, "Line: %d: if(>=) goto %d (true)", _currentLine, labelIdx);
		_currentPosition = _currentScriptElement->stack[labelIdx];
	} else {
		debugC(5, kCineDebugScript, "Line: %d: if(>=) goto %d (false)", _currentLine, labelIdx);
	}
}

void o1_gotoIfInf() {
	byte labelIdx = getNextByte();

	if (_currentScriptElement->compareResult == kCmpLT) {
		assert(_currentScriptElement->stack[labelIdx] != -1);

		debugC(5, kCineDebugScript, "Line: %d: if(<) goto %d (true)", _currentLine, labelIdx);
		_currentPosition = _currentScriptElement->stack[labelIdx];
	} else {
		debugC(5, kCineDebugScript, "Line: %d: if(<) goto %d (false)", _currentLine, labelIdx);
	}
}

void o1_gotoIfInfEqu() {
	byte labelIdx = getNextByte();

	if (_currentScriptElement->compareResult & (kCmpLT | kCmpEQ)) {
		assert(_currentScriptElement->stack[labelIdx] != -1);

		debugC(5, kCineDebugScript, "Line: %d: if(<=) goto %d (true)", _currentLine, labelIdx);
		_currentPosition = _currentScriptElement->stack[labelIdx];
	} else {
		debugC(5, kCineDebugScript, "Line: %d: if(<=) goto %d (false)", _currentLine, labelIdx);
	}
}

void o1_gotoIfEqu() {
	byte labelIdx = getNextByte();

	if (_currentScriptElement->compareResult == kCmpEQ) {
		assert(_currentScriptElement->stack[labelIdx] != -1);

		debugC(5, kCineDebugScript, "Line: %d: if(==) goto %d (true)", _currentLine, labelIdx);
		_currentPosition = _currentScriptElement->stack[labelIdx];
	} else {
		debugC(5, kCineDebugScript, "Line: %d: if(==) goto %d (false)", _currentLine, labelIdx);
	}
}

void o1_gotoIfDiff() {
	byte labelIdx = getNextByte();

	if (_currentScriptElement->compareResult != kCmpEQ) {
		assert(_currentScriptElement->stack[labelIdx] != -1);

		debugC(5, kCineDebugScript, "Line: %d: if(!=) goto %d (true)", _currentLine, labelIdx);
		_currentPosition = _currentScriptElement->stack[labelIdx];
	} else {
		debugC(5, kCineDebugScript, "Line: %d: if(!=) goto %d (false)", _currentLine, labelIdx);
	}
}

void o1_removeLabel() {
	byte labelIdx = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: removeLabel(%d)", _currentLine, labelIdx);
	_currentScriptElement->stack[labelIdx] = -1;
}

void o1_loop() {
	byte varIdx = getNextByte();
	byte labelIdx = getNextByte();

	_currentScriptElement->localVars[varIdx]--;

	if (_currentScriptElement->localVars[varIdx] >= 0) {
		assert(_currentScriptElement->stack[labelIdx] != -1);

		debugC(5, kCineDebugScript, "Line: %d: loop(var[%d]) goto %d (continue)", _currentLine, varIdx, labelIdx);
		_currentPosition = _currentScriptElement->stack[labelIdx];
	} else {
		debugC(5, kCineDebugScript, "Line: %d: loop(var[%d]) goto %d (stop)", _currentLine, varIdx, labelIdx);
	}
}

void o1_startGlobalScript() {
	// OP_startScript
	byte param = getNextByte();

	assert(param < NUM_MAX_SCRIPT);

	debugC(5, kCineDebugScript, "Line: %d: startScript(%d)", _currentLine, param);
	addScriptToList0(param);
}

void o1_endGlobalScript() {
	byte scriptIdx = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: stopGlobalScript(%d)", _currentLine, scriptIdx);
	stopGlobalScript(scriptIdx);
}

void o1_loadAnim() {
	// OP_loadResource
	const char *param = getNextString();

	debugC(5, kCineDebugScript, "Line: %d: loadResource(\"%s\")", _currentLine, param);
	loadResource(param);
}

void o1_loadBg() {
	const char *param = getNextString();

	debugC(5, kCineDebugScript, "Line: %d: loadBg(\"%s\")", _currentLine, param);

	loadBg(param);
	freeBgIncrustList();
	bgVar0 = 0;
}

void o1_loadCt() {
	const char *param = getNextString();

	debugC(5, kCineDebugScript, "Line: %d: loadCt(\"%s\")", _currentLine, param);
	loadCt(param);
}

void o1_loadPart() {
	const char *param = getNextString();

	debugC(5, kCineDebugScript, "Line: %d: loadPart(\"%s\")", _currentLine, param);
	loadPart(param);
}

void o1_closePart() {
	debugC(5, kCineDebugScript, "Line: %d: closePart", _currentLine);
	closePart();
}

void o1_loadNewPrcName() {
	// OP_loadData
	byte param1 = getNextByte();
	const char *param2 = getNextString();

	assert(param1 <= 3);

	switch (param1) {
	case 0:
		debugC(5, kCineDebugScript, "Line: %d: loadPrc(\"%s\")", _currentLine, param2);
		strcpy(newPrcName, param2);
		break;
	case 1:
		debugC(5, kCineDebugScript, "Line: %d: loadRel(\"%s\")", _currentLine, param2);
		strcpy(newRelName, param2);
		break;
	case 2:
		debugC(5, kCineDebugScript, "Line: %d: loadObject(\"%s\")", _currentLine, param2);
		strcpy(newObjectName, param2);
		break;
	case 3:
		debugC(5, kCineDebugScript, "Line: %d: loadMsg(\"%s\")", _currentLine, param2);
		strcpy(newMsgName, param2);
		break;
	}
}

void o1_requestCheckPendingDataLoad() {
	debugC(5, kCineDebugScript, "Line: %d: request data load", _currentLine);
	checkForPendingDataLoadSwitch = 1;
}

void o1_blitAndFade() {
	debugC(5, kCineDebugScript, "Line: %d: request fadein", _currentLine);
	// TODO: use real code

	drawOverlays();
	fadeRequired = true;
	flip();
}

void o1_fadeToBlack() {
	debugC(5, kCineDebugScript, "Line: %d: request fadeout", _currentLine);

	fadeToBlack();
}

void o1_transformPaletteRange() {
	byte startColor = getNextByte();
	byte numColor = getNextByte();
	uint16 r = getNextWord();
	uint16 g = getNextWord();
	uint16 b = getNextWord();

	debugC(5, kCineDebugScript, "Line: %d: transformPaletteRange(from:%d,numIdx:%d,r:%d,g:%d,b:%d)", _currentLine, startColor, numColor, r, g, b);

	transformPaletteRange(startColor, numColor, r, g, b);
}

void o1_setDefaultMenuColor2() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuColor2(%d)", _currentLine, param);
	defaultMenuBoxColor2 = param;
}

void o1_palRotate() {
	byte a = getNextByte();
	byte b = getNextByte();
	byte c = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: palRotate(%d,%d,%d)", _currentLine, a, b, c);
	palRotate(a, b, c);
}

void o1_break() {
	debugC(5, kCineDebugScript, "Line: %d: break", _currentLine);

	_currentScriptElement->scriptPosition = _currentPosition;
	_closeScript = 1;
}

void o1_endScript() {
	debugC(5, kCineDebugScript, "Line: %d: endScript", _currentLine);

	if (_currentScriptParams == 0) {
		endScript0(_currentScriptElement->scriptIdx);
	} else {
		endScript1(_currentScriptElement->scriptIdx);
	}

	_closeScript = 1;
}

void o1_message() {
	byte param1 = getNextByte();
	uint16 param2 = getNextWord();
	uint16 param3 = getNextWord();
	uint16 param4 = getNextWord();
	uint16 param5 = getNextWord();

	debugC(5, kCineDebugScript, "Line: %d: message(%d,%d,%d,%d,%d)", _currentLine, param1, param2, param3, param4, param5);

	addMessage(param1, param2, param3, param4, param5);
}

void o1_loadGlobalVar() {
	byte varIdx = getNextByte();
	byte varType = getNextByte();

	if (varType) {
		byte dataIdx = getNextByte();

		if (varType == 1) {
			debugC(5, kCineDebugScript, "Line: %d: globalVars[%d] = var[%d]", _currentLine, varIdx, dataIdx);
			globalVars[varIdx] = _currentScriptElement->localVars[dataIdx];
		} else {
			debugC(5, kCineDebugScript, "Line: %d: globalVars[%d] = globalVars[%d]", _currentLine, varIdx, dataIdx);
			globalVars[varIdx] = globalVars[dataIdx];
		}
	} else {
		uint16 value = getNextWord();

		debugC(5, kCineDebugScript, "Line: %d: globalVars[%d] = %d", _currentLine, varIdx, value);
		globalVars[varIdx] = value;
	}
}

void o1_compareGlobalVar() {
	byte varIdx = getNextByte();
	byte varType = getNextByte();

	if (varType) {
		byte value = getNextByte();

		debugC(5, kCineDebugScript, "Line: %d: compare globalVars[%d] and var[%d]", _currentLine, varIdx, value);
		_currentScriptElement->compareResult = compareVars(globalVars[varIdx], _currentScriptElement->localVars[value]);
	} else {
		uint16 value = getNextWord();

		debugC(5, kCineDebugScript, "Line: %d: compare globalVars[%d] and %d", _currentLine, varIdx, value);

		if (varIdx == 255 && (g_cine->getGameType() == Cine::GType_FW)) {	// TODO: fix
			_currentScriptElement->compareResult = 1;
		} else {
			_currentScriptElement->compareResult = compareVars(globalVars[varIdx], value);
		}
	}
}

void o1_declareFunctionName() {
	const char *param = getNextString();

	debugC(5, kCineDebugScript, "Line: %d: comment(%s)", _currentLine, param);
}

void o1_freePartRange() {
	byte startIdx = getNextByte();
	byte numIdx = getNextByte();

	assert(startIdx + numIdx <= NUM_MAX_ANIMDATA);

	debugC(5, kCineDebugScript, "Line: %d: freePartRange(%d,%d)", _currentLine, startIdx, numIdx);
	freeAnimDataRange(startIdx, numIdx);
}

void o1_unloadAllMasks() {
	debugC(5, kCineDebugScript, "Line: %d: unloadAllMasks()", _currentLine);
	unloadAllMasks();
}

void o1_setScreenDimensions() {
	warning("STUB: o1_setScreenDimensions()");
	getNextWord();
	getNextWord();
	getNextWord();
	getNextWord();
	// setupScreenParam
}

void o1_displayBackground() {
	warning("STUB: o1_displayBackground()");
}

void o1_initializeZoneData() {
	debugC(5, kCineDebugScript, "Line: %d: initializeZoneData()", _currentLine);

	for (int i = 0; i < NUM_MAX_ZONE; i++) {
		zoneData[i] = i;
	}
}

void o1_setZoneDataEntry() {
	byte zoneIdx = getNextByte();
	uint16 var = getNextWord();

	debugC(5, kCineDebugScript, "Line: %d: setZone[%d] = %d", _currentLine, zoneIdx, var);
	zoneData[zoneIdx] = var;
}

void o1_getZoneDataEntry() {
	byte zoneIdx = getNextByte();
	byte var = getNextByte();

	_currentScriptElement->localVars[var] = zoneData[zoneIdx];
}

void o1_setDefaultMenuColor() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuColor(%d)", _currentLine, param);
	defaultMenuBoxColor = param;
}

void o1_allowPlayerInput() {
	debugC(5, kCineDebugScript, "Line: %d: allowPlayerInput()", _currentLine);
	allowPlayerInput = 1;
}

void o1_disallowPlayerInput() {
	debugC(5, kCineDebugScript, "Line: %d: dissallowPlayerInput()", _currentLine);
	allowPlayerInput = 0;
}

void o1_changeDataDisk() {
	byte newDisk = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: changeDataDisk(%d)", _currentLine, newDisk);
	checkDataDisk(newDisk);
}

void o1_loadMusic() {
	const char *param = getNextString();

	debugC(5, kCineDebugScript, "Line: %d: loadMusic(%s)", _currentLine, param);
	g_sound->loadMusic(param);
}

void o1_playMusic() {
	debugC(5, kCineDebugScript, "Line: %d: playMusic()", _currentLine);
	g_sound->playMusic();
}

void o1_fadeOutMusic() {
	debugC(5, kCineDebugScript, "Line: %d: fadeOutMusic()", _currentLine);
	g_sound->fadeOutMusic();
}

void o1_stopSample() {
	debugC(5, kCineDebugScript, "Line: %d: stopSample()", _currentLine);
	g_sound->stopMusic();
}

void o1_op71() {
	warning("STUB: o1_op71()");
	getNextByte();
	getNextWord();
}

void o1_op72() {
	warning("STUB: o1_op72()");
	getNextWord();
	getNextByte();
	getNextWord();
}

void o1_op73() {
	// I believe this opcode is identical to o1_op72(). In fact, Operation
	// Stealth doesn't even have it. It uses o1_op72() instead.
	warning("STUB: o1_op73()");
	getNextWord();
	getNextByte();
	getNextWord();
}

void o1_playSample() {
	debugC(5, kCineDebugScript, "Line: %d: playSample()", _currentLine);

	byte anim = getNextByte();
	byte channel = getNextByte();

	uint16 freq = getNextWord();
	byte repeat = getNextByte();

	int16 volume = getNextWord();
	uint16 size = getNextWord();

	if (!animDataTable[anim].ptr1) {
		return;
	}

	if (g_cine->getPlatform() == Common::kPlatformAmiga || g_cine->getPlatform() == Common::kPlatformAtariST) {
		if (size == 0xFFFF) {
			size = animDataTable[anim].width * animDataTable[anim].height;
		}
		if (channel < 10) { // || _currentOpcode == 0x78
			int channel1, channel2;
			if (channel == 0) {
				channel1 = 0;
				channel2 = 1;
			} else {
				channel1 = 2;
				channel2 = 3;
			}
			g_sound->playSound(channel1, freq, animDataTable[anim].ptr1, size, -1, volume, 63, repeat);
			g_sound->playSound(channel2, freq, animDataTable[anim].ptr1, size,  1, volume,  0, repeat);
		} else {
			channel -= 10;
			if (volume > 63) {
				volume = 63;
			}
			g_sound->playSound(channel, freq, animDataTable[anim].ptr1, size, 0, 0, volume, repeat);
		}
	} else {
		if (volume > 63 || volume < 0) {
			volume = 63;
		}
		if (channel >= 10) {
			channel -= 10;
		}
		if (volume < 50) {
			volume = 50;
		}
		if (g_cine->getGameType() == Cine::GType_OS && size == 0) {
			return;
		}
		g_sound->stopMusic();
		if (size == 0xFFFF) {
			g_sound->playSound(channel, 0, animDataTable[anim].ptr1, 0, 0, 0, volume, 0);
		} else {
			g_sound->stopSound(channel);
		}
	}
}

void o1_disableSystemMenu() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: disableSystemMenu(%d)", _currentLine, param);
	disableSystemMenu = (param != 0);
}

void o1_loadMask5() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: addOverlay5(%d)", _currentLine, param);
	loadOverlayElement(param, 5);
}

void o1_unloadMask5() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: freeOverlay5(%d)", _currentLine, param);
	freeOverlay(param, 5);
}

// ------------------------------------------------------------------------
// OPERATION STEALTH opcodes
// ------------------------------------------------------------------------

void o2_loadPart() {
	const char *param = getNextString();

	debugC(5, kCineDebugScript, "Line: %d: loadPart(\"%s\")", _currentLine, param);
}

void o2_playSample() {
	if (g_cine->getPlatform() == Common::kPlatformAmiga || g_cine->getPlatform() == Common::kPlatformAtariST) {
		// no-op in these versions
		getNextByte();
		getNextByte();
		getNextWord();
		getNextByte();
		getNextWord();
		getNextWord();
		return;
	}
	o1_playSample();
}

void o2_playSampleAlt() {
	byte num = getNextByte();
	byte channel = getNextByte();
	uint16 frequency = getNextWord();
	getNextByte();
	getNextWord();
	uint16 size = getNextWord();

	if (size == 0xFFFF) {
		size = animDataTable[num].width * animDataTable[num].height;
	}
	if (animDataTable[num].ptr1) {
		if (g_cine->getPlatform() == Common::kPlatformPC) {
			// if speaker output is available, play sound on it
			// if it's another device, don't play anything
			// TODO: implement this, it's used in the introduction for example
			// on each letter displayed
		} else {
			g_sound->playSound(channel, frequency, animDataTable[num].ptr1, size, 0, 0, 63, 0);
		}
	}
}

void o2_addSeqListElement() {
	byte param1 = getNextByte();
	byte param2 = getNextByte();
	byte param3 = getNextByte();
	byte param4 = getNextByte();
	uint16 param5 = getNextWord();
	uint16 param6 = getNextWord();
	uint16 param7 = getNextWord();

	debugC(5, kCineDebugScript, "Line: %d: addSeqListElement(%d,%d,%d,%d,%d,%d,%d)", _currentLine, param1, param2, param3, param4, param5, param6, param7);
	addSeqListElement(param1, 0, param2, param3, param4, param5, param6, 0, param7);
}

void o2_removeSeq() {
	byte a = getNextByte();
	byte b = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: removeSeq(%d,%d) -> TODO", _currentLine, a, b);
	removeSeq(a, 0, b);
}

void o2_op81() {
	warning("STUB: o2_op81()");
	// freeUnkList();
}

void o2_op82() {
	warning("STUB: o2_op82()");
	getNextByte();
	getNextByte();
	getNextWord();
}

void o2_isSeqRunning() {
	byte a = getNextByte();
	byte b = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: OP83(%d,%d) -> TODO", _currentLine, a, b);

	if (isSeqRunning(a, 0, b)) {
		_currentScriptElement->compareResult = 1;
	} else {
		_currentScriptElement->compareResult = 0;
	}
}

void o2_gotoIfSupNearest() {
	byte labelIdx = getNextByte();

	if (_currentScriptElement->compareResult == kCmpGT) {
		assert(_currentScriptElement->stack[labelIdx] != -1);

		debugC(5, kCineDebugScript, "Line: %d: if(>) goto nearest %d (true)", _currentLine, labelIdx);
		_currentPosition = computeScriptStackFromScript(_currentScriptElement->scriptPtr, _currentPosition, labelIdx, scriptTable[_currentScriptElement->scriptIdx].size);
	} else {
		debugC(5, kCineDebugScript, "Line: %d: if(>) goto nearest %d (false)", _currentLine, labelIdx);
	}
}

void o2_gotoIfSupEquNearest() {
	byte labelIdx = getNextByte();

	if (_currentScriptElement->compareResult & (kCmpGT | kCmpEQ)) {
		assert(_currentScriptElement->stack[labelIdx] != -1);

		debugC(5, kCineDebugScript, "Line: %d: if(>=) goto nearest %d (true)", _currentLine, labelIdx);
		_currentPosition = computeScriptStackFromScript(_currentScriptElement->scriptPtr, _currentPosition, labelIdx, scriptTable[_currentScriptElement->scriptIdx].size);
	} else {
		debugC(5, kCineDebugScript, "Line: %d: if(>=) goto nearest %d (false)", _currentLine, labelIdx);
	}
}

void o2_gotoIfInfNearest() {
	byte labelIdx = getNextByte();

	if (_currentScriptElement->compareResult == kCmpLT) {
		assert(_currentScriptElement->stack[labelIdx] != -1);

		debugC(5, kCineDebugScript, "Line: %d: if(<) goto nearest %d (true)", _currentLine, labelIdx);
		_currentPosition = computeScriptStackFromScript(_currentScriptElement->scriptPtr, _currentPosition, labelIdx, scriptTable[_currentScriptElement->scriptIdx].size);
	} else {
		debugC(5, kCineDebugScript, "Line: %d: if(<) goto nearest %d (false)", _currentLine, labelIdx);
	}
}

void o2_gotoIfInfEquNearest() {
	byte labelIdx = getNextByte();

	if (_currentScriptElement->compareResult & (kCmpLT | kCmpEQ)) {
		assert(_currentScriptElement->stack[labelIdx] != -1);

		debugC(5, kCineDebugScript, "Line: %d: if(<=) goto nearest %d (true)", _currentLine, labelIdx);
		_currentPosition = computeScriptStackFromScript(_currentScriptElement->scriptPtr, _currentPosition, labelIdx, scriptTable[_currentScriptElement->scriptIdx].size);
	} else {
		debugC(5, kCineDebugScript, "Line: %d: if(<=) goto nearest %d (false)", _currentLine, labelIdx);
	}
}

void o2_gotoIfEquNearest() {
	byte labelIdx = getNextByte();

	if (_currentScriptElement->compareResult == kCmpEQ) {
		assert(_currentScriptElement->stack[labelIdx] != -1);

		debugC(5, kCineDebugScript, "Line: %d: if(==) goto nearest %d (true)", _currentLine, labelIdx);
		_currentPosition = computeScriptStackFromScript(_currentScriptElement->scriptPtr, _currentPosition, labelIdx, scriptTable[_currentScriptElement->scriptIdx].size);
	} else {
		debugC(5, kCineDebugScript, "Line: %d: if(==) goto nearest %d (false)", _currentLine, labelIdx);
	}
}

void o2_gotoIfDiffNearest() {
	byte labelIdx = getNextByte();

	if (_currentScriptElement->compareResult != kCmpEQ) {
		assert(_currentScriptElement->stack[labelIdx] != -1);

		debugC(5, kCineDebugScript, "Line: %d: if(!=) goto nearest %d (true)", _currentLine, labelIdx);
		_currentPosition = computeScriptStackFromScript(_currentScriptElement->scriptPtr, _currentPosition, labelIdx, scriptTable[_currentScriptElement->scriptIdx].size);
	} else {
		debugC(5, kCineDebugScript, "Line: %d: if(!=) goto nearest %d (false)", _currentLine, labelIdx);
	}
}

void o2_startObjectScript() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: startObjectScript(%d)", _currentLine, param);
	runObjectScript(param);
}

void o2_stopObjectScript() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: stopObjectScript(%d)", _currentLine, param);
	stopObjectScript(param);
}

void o2_op8D() {
	warning("STUB: o2_op8D()");
	getNextWord();
	getNextWord();
	getNextWord();
	getNextWord();
	getNextWord();
	getNextWord();
	getNextWord();
	getNextWord();
	// _currentScriptElement->compareResult = ...
}

void o2_addBackground() {
	byte param1 = getNextByte();
	const char *param2 = getNextString();

	debugC(5, kCineDebugScript, "Line: %d: addBackground(%s,%d)", _currentLine, param2, param1);
	addBackground(param2, param1);
}

void o2_removeBackground() {
	byte param = getNextByte();

	assert(param);

	debugC(5, kCineDebugScript, "Line: %d: removeBackground(%d)", _currentLine, param);

	if (additionalBgTable[param]) {
		free(additionalBgTable[param]);
		additionalBgTable[param] = NULL;
	}

	if (currentAdditionalBgIdx == param) {
		currentAdditionalBgIdx = 0;
	}

	if (currentAdditionalBgIdx2 == param) {
		currentAdditionalBgIdx2 = 0;
	}

	strcpy(currentBgName[param], "");
}

void o2_loadAbs() {
	byte param1 = getNextByte();
	const char *param2 = getNextString();

	debugC(5, kCineDebugScript, "Line: %d: loadABS(%d,%s)", _currentLine, param1, param2);
	loadAbs(param2, param1);
}

void o2_loadBg() {
	byte param = getNextByte();

	assert(param <= 8);

	debugC(5, kCineDebugScript, "Line: %d: useBg(%d)", _currentLine, param);

	if (additionalBgTable[param]) {
		currentAdditionalBgIdx = param;
		//if (_screenNeedFadeOut == 0) {
		//	adBgVar1 = 1;
		//}
		fadeRequired = true;
	}
}

void o2_wasZoneChecked() {
	warning("STUB: o2_wasZoneChecked()");
}

void o2_op9B() {
	warning("STUB: o2_op9B()");
	getNextWord();
	getNextWord();
	getNextWord();
	getNextWord();
	getNextWord();
	getNextWord();
	getNextWord();
	getNextWord();
}

void o2_op9C() {
	warning("STUB: o2_op9C()");
	getNextWord();
	getNextWord();
	getNextWord();
	getNextWord();
}

void o2_useBgScroll() {
	byte param = getNextByte();

	assert(param <= 8);

	debugC(5, kCineDebugScript, "Line: %d: useBgScroll(%d)", _currentLine, param);

	if (additionalBgTable[param]) {
		currentAdditionalBgIdx2 = param;
	}
}

void o2_setAdditionalBgVScroll() {
	byte param1 = getNextByte();

	if (param1) {
		byte param2 = getNextByte();

		debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = var[%d]", _currentLine, param2);
		additionalBgVScroll = _currentScriptElement->localVars[param2];
	} else {
		uint16 param2 = getNextWord();

		debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = %d", _currentLine, param2);
		additionalBgVScroll = param2;
	}
}

void o2_op9F() {
	warning("o2_op9F()");
	getNextWord();
	getNextWord();
}

void o2_addGfxElementA0() {
	uint16 param1 = getNextWord();
	uint16 param2 = getNextWord();

	debugC(5, kCineDebugScript, "Line: %d: addGfxElementA0(%d,%d)", _currentLine, param1, param2);
	addGfxElementA0(param1, param2);
}

void o2_opA1() {
	warning("STUB: o2_opA1()");
	getNextWord();
	getNextWord();
	// removeGfxElementA0( ... );
}

void o2_opA2() {
	warning("STUB: o2_opA2()");
	getNextWord();
	getNextWord();
	// addGfxElementA2();
}

void o2_opA3() {
	warning("STUB: o2_opA3()");
	getNextWord();
	getNextWord();
	// removeGfxElementA2();
}

void o2_loadMask22() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: addOverlay22(%d)", _currentLine, param);
	loadOverlayElement(param, 22);
}

void o2_unloadMask22() {
	byte param = getNextByte();

	debugC(5, kCineDebugScript, "Line: %d: removeOverlay22(%d)", _currentLine, param);
	freeOverlay(param, 22);
}

// ------------------------------------------------------------------------

void executeScript(prcLinkedListStruct *scriptElement, uint16 params) {
	assert(scriptElement);

	if (scriptElement->scriptIdx == -1) {
		return;
	}

	assert(scriptElement->scriptPtr);

	_currentScriptElement = scriptElement;
	_currentScriptParams = params;
	_currentScriptPtr = scriptElement->scriptPtr;
	_currentPosition = scriptElement->scriptPosition;

	_closeScript = 0;

	while (!_closeScript) {
		_currentLine = _currentPosition;

		byte opcode = getNextByte();

		if (opcode && opcode < _numOpcodes) {
			if (_opcodeTable[opcode - 1].proc)
				(_opcodeTable[opcode - 1].proc) ();
			else
				warning("Undefined opcode 0x%02X in executeScript", opcode - 1);
		}
	}
}

void executeList1(void) {
	prcLinkedListStruct *currentHead = objScriptList.next;

	while (currentHead) {
		prcLinkedListStruct *tempHead;

		tempHead = currentHead->next;

		executeScript(currentHead, 1);

		currentHead = tempHead;
	}
}

void executeList0(void) {
	prcLinkedListStruct *currentHead = globalScriptsHead.next;

	while (currentHead) {
		prcLinkedListStruct *tempHead;

		executeScript(currentHead, 0);

		tempHead = currentHead->next;
		currentHead = tempHead;
	}
}

void purgeList1(void) {
}

void purgeList0(void) {
}

////////////////////////////////////
// SCRIPT DECOMPILER

#ifdef DUMP_SCRIPTS

char decompileBuffer[10000][1000];
uint16 decompileBufferPosition = 0;

char bufferDec[256];

char compareString1[256];
char compareString2[256];

const char *getObjPramName(byte paramIdx) {
	switch (paramIdx) {
	case 1:
		return ".X";
	case 2:
		return ".Y";
	case 3:
		return ".mask";
	case 4:
		return ".frame";
	case 5:
		return ".status";
	case 6:
		return ".costume";
	default:
		sprintf(bufferDec, ".param%d", paramIdx);
		return bufferDec;
	}
}

void decompileScript(byte *scriptPtr, int16 *stackPtr, uint16 scriptSize, uint16 scriptIdx) {
	char lineBuffer[256];
	byte *localScriptPtr = scriptPtr;
	uint16 exitScript;
	uint32 position = 0;

	assert(scriptPtr);
	// assert(stackPtr);

	exitScript = 0;

	sprintf(decompileBuffer[decompileBufferPosition++], "--------- SCRIPT %d ---------\n", scriptIdx);

	do {
		uint16 opcode = *(localScriptPtr + position);
		position++;

		if (position == scriptSize) {
			opcode = 0;
		}

		strcpy(lineBuffer, "");

		switch (opcode - 1) {
		case -1:
			{
				break;
			}
		case 0x0:
			{
				byte param1;
				byte param2;
				int16 param3;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				param3 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				sprintf(lineBuffer, "obj[%d]%s = %d\n", param1, getObjPramName(param2), param3);

				break;
			}
		case 0x1:
			{
				byte param1;
				byte param2;
				byte param3;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				param3 = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "var[%d]=obj[%d]%s\n", param3, param1, getObjPramName(param2));
				break;
			}
		case 0x2:
		case 0x3:
		case 0x4:
		case 0x5:
		case 0x6:
			{
				byte param1;
				byte param2;
				int16 param3;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				param3 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				if (opcode - 1 == 0x2) {
					sprintf(lineBuffer, "obj[%d]%s+=%d\n", param1, getObjPramName(param2), param3);
				} else if (opcode - 1 == 0x3) {
					sprintf(lineBuffer, "obj[%d]%s-=%d\n", param1, getObjPramName(param2), param3);
				} else if (opcode - 1 == 0x4) {
					sprintf(lineBuffer, "obj[%d]%s+=obj[%d]%s\n", param1, getObjPramName(param2), param3, getObjPramName(param2));
				} else if (opcode - 1 == 0x5) {
					sprintf(lineBuffer, "obj[%d]%s-=obj[%d]%s\n", param1, getObjPramName(param2), param3, getObjPramName(param2));
				} else if (opcode - 1 == 0x6) {
					sprintf(compareString1, "obj[%d]%s", param1, getObjPramName(param2));
					sprintf(compareString2, "%d", param3);
				}
				break;
			}
		case 0x7:
		case 0x8:
			{
				byte param1;
				int16 param2;
				int16 param3;
				int16 param4;
				int16 param5;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param3 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param4 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param5 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				if (opcode - 1 == 0x7) {
					sprintf(lineBuffer, "setupObject(Idx:%d,X:%d,Y:%d,mask:%d,frame:%d)\n", param1, param2, param3, param4, param5);
				} else if (opcode - 1 == 0x8) {
					sprintf(lineBuffer, "checkCollision(%d,%d,%d,%d,%d)\n", param1, param2, param3, param4, param5);
				}
				break;
			}
		case 0x9:
			{
				byte param1;
				int16 param2;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				if (param2) {
					byte param3;

					param3 = *(localScriptPtr + position);
					position++;

					if (param2 == 1) {
						sprintf(lineBuffer, "var[%d]=var[%d]\n", param1, param3);
					} else if (param2 == 2) {
						sprintf(lineBuffer, "var[%d]=globalVar[%d]\n", param1, param3);
					} else if (param2 == 3) {
						sprintf(lineBuffer, "var[%d]=mouse.X\n", param1);
					} else if (param2 == 4) {
						sprintf(lineBuffer, "var[%d]=mouse.Y\n", param1);
					} else if (param2 == 5) {
						sprintf(lineBuffer, "var[%d]=rand() mod %d\n", param1, param3);
					} else if (param2 == 8) {
						sprintf(lineBuffer, "var[%d]=file[%d].packedSize\n", param1, param3);
					} else if (param2 == 9) {
						sprintf(lineBuffer, "var[%d]=file[%d].unpackedSize\n", param1, param3);
					} else {
						error("decompileScript: 0x09: param2 = %d", param2);
					}
				} else {
					int16 param3;

					param3 = READ_BE_UINT16(localScriptPtr + position);
					position += 2;

					sprintf(lineBuffer, "var[%d]=%d\n", param1, param3);
				}

				break;
			}
		case 0xA:
		case 0xB:
		case 0xC:
		case 0xD:
			{
				byte param1;
				byte param2;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				if (param2) {
					byte param3;

					param3 = *(localScriptPtr + position);
					position++;

					if (opcode - 1 == 0xA) {
						sprintf(lineBuffer, "var[%d]+=var[%d]\n", param1, param3);
					} else if (opcode - 1 == 0xB) {
						sprintf(lineBuffer, "var[%d]-=var[%d]\n", param1, param3);
					} else if (opcode - 1 == 0xC) {
						sprintf(lineBuffer, "var[%d]*=var[%d]\n", param1, param3);
					} else if (opcode - 1 == 0xD) {
						sprintf(lineBuffer, "var[%d]/=var[%d]\n", param1, param3);
					}
				} else {
					int16 param3;

					param3 = READ_BE_UINT16(localScriptPtr +  position);
					position += 2;

					if (opcode - 1 == 0xA) {
						sprintf(lineBuffer, "var[%d]+=%d\n", param1, param3);
					} else if (opcode - 1 == 0xB) {
						sprintf(lineBuffer, "var[%d]-=%d\n", param1, param3);
					} else if (opcode - 1 == 0xC) {
						sprintf(lineBuffer, "var[%d]*=%d\n", param1, param3);
					} else if (opcode - 1 == 0xD) {
						sprintf(lineBuffer, "var[%d]/=%d\n", param1, param3);
					}
				}
				break;
			}
		case 0xE:
			{
				byte param1;
				byte param2;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				if (param2) {
					byte param3;

					param3 = *(localScriptPtr + position);
					position++;

					if (param2 == 1) {
						sprintf(compareString1, "var[%d]", param1);
						sprintf(compareString2, "var[%d]", param3);

					} else if (param2 == 2) {
						sprintf(compareString1, "var[%d]", param1);
						sprintf(compareString2, "globalVar[%d]", param3);
					} else {
						error("decompileScript: 0x0E: param2 = %d", param2);
					}
				} else {
					int16 param3;

					param3 = READ_BE_UINT16(localScriptPtr + position);
					position += 2;

					sprintf(compareString1, "var[%d]", param1);
					sprintf(compareString2, "%d", param3);
				}
				break;
			}
		case 0xF:
			{
				byte param1;
				byte param2;
				byte param3;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				param3 = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "obj[%d]%s=var[%d]\n", param1, getObjPramName(param2), param3);

				break;
			}
		case 0x13:
		case 0x14:
		case 0x15:
		case 0x16:
		case 0x17:
		case 0x18:
		case 0x19:
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				if (opcode - 1 == 0x13) {
					sprintf(lineBuffer, "loadMask0(%d)\n", param);
				} else if (opcode - 1 == 0x14) {
					sprintf(lineBuffer, "unloadMask0(%d)\n", param);
				} else if (opcode - 1 == 0x15) {
					sprintf(lineBuffer, "OP_15(%d)\n", param);
				} else if (opcode - 1 == 0x16) {
					sprintf(lineBuffer, "loadMask1(%d)\n", param);
				} else if (opcode - 1 == 0x17) {
					sprintf(lineBuffer, "unloadMask0(%d)\n", param);
				} else if (opcode - 1 == 0x18) {
					sprintf(lineBuffer, "loadMask4(%d)\n", param);
				} else if (opcode - 1 == 0x19) {
					sprintf(lineBuffer, "unloadMask4(%d)\n", param);
				}
				break;
			}
		case 0x1A:
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "OP_1A(%d)\n", param);

				break;
			}
		case 0x1B:
			{
				sprintf(lineBuffer, "freeBgIncrustList()\n");
				break;
			}
		case 0x1D:
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "label(%d)\n", param);

				break;
			}
		case 0x1E:
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "goto(%d)\n", param);

				break;
			}
		// If cases
		case 0x1F:
		case 0x20:
		case 0x21:
		case 0x22:
		case 0x23:
		case 0x24:
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				if (opcode - 1 == 0x1F) {
					sprintf(lineBuffer, "if(%s>%s) goto(%d)\n", compareString1, compareString2, param);
				} else if (opcode - 1 == 0x20) {
					sprintf(lineBuffer, "if(%s>=%s) goto(%d)\n", compareString1, compareString2, param);
				} else if (opcode - 1 == 0x21) {
					sprintf(lineBuffer, "if(%s<%s) goto(%d)\n", compareString1, compareString2, param);
				} else if (opcode - 1 == 0x22) {
					sprintf(lineBuffer, "if(%s<=%s) goto(%d)\n", compareString1, compareString2, param);
				} else if (opcode - 1 == 0x23) {
					sprintf(lineBuffer, "if(%s==%s) goto(%d)\n", compareString1, compareString2, param);
				} else if (opcode - 1 == 0x24) {
					sprintf(lineBuffer, "if(%s!=%s) goto(%d)\n", compareString1, compareString2, param);
				}
				break;
			}
		case 0x25:
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "removeLabel(%d)\n", param);

				break;
			}
		case 0x26:
			{
				byte param1;
				byte param2;

				param1 = *(localScriptPtr + position);
				position++;
				param2 = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "loop(--var[%d]) -> label(%d)\n", param1, param2);

				break;
			}
		case 0x31:
		case 0x32:
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				if (opcode - 1 == 0x31) {
					sprintf(lineBuffer, "startGlobalScript(%d)\n", param);
				} else if (opcode - 1 == 0x32) {
					sprintf(lineBuffer, "endGlobalScript(%d)\n", param);
				}
				break;
			}
		case 0x3B:
		case 0x3C:
		case 0x3D:
		case OP_loadPart:
			{
				if (opcode - 1 == 0x3B) {
					sprintf(lineBuffer, "loadResource(%s)\n", localScriptPtr + position);
				} else if (opcode - 1 == 0x3C) {
					sprintf(lineBuffer, "loadBg(%s)\n",	localScriptPtr + position);
				} else if (opcode - 1 == 0x3D) {
					sprintf(lineBuffer, "loadCt(%s)\n", localScriptPtr + position);
				} else if (opcode - 1 == OP_loadPart) {
					sprintf(lineBuffer, "loadPart(%s)\n", localScriptPtr + position);
				}

				position += strlen((char *)localScriptPtr + position) + 1;
				break;
			}
		case 0x40:
			{
				sprintf(lineBuffer, "closePart()\n");
				break;
			}
		case OP_loadNewPrcName:
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "loadPrc(%d,%s)\n", param, localScriptPtr + position);

				position += strlen((char *)localScriptPtr + position) + 1;
				break;
			}
		case OP_requestCheckPendingDataLoad:	// nop
			{
				sprintf(lineBuffer, "requestCheckPendingDataLoad()\n");
				break;
			}
		case 0x45:
			{
				sprintf(lineBuffer, "blitAndFade()\n");
				break;
			}
		case 0x46:
			{
				sprintf(lineBuffer, "fadeToBlack()\n");
				break;
			}
		case 0x47:
			{
				byte param1;
				byte param2;
				int16 param3;
				int16 param4;
				int16 param5;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				param3 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param4 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param5 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				sprintf(lineBuffer, "transformPaletteRange(%d,%d,%d,%d,%d)\n", param1, param2, param3, param4, param5);

				break;
			}
		case 0x49:
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "setDefaultMenuColor2(%d)\n", param);

				break;
			}
		case 0x4F:
			{
				sprintf(lineBuffer, "break()\n");
				exitScript = 1;
				break;
			}
		case 0x50:
			{
				sprintf(lineBuffer, "endScript()\n\n");
				break;
			}
		case 0x51:
			{
				byte param1;
				int16 param2;
				int16 param3;
				int16 param4;
				int16 param5;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param3 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param4 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param5 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				sprintf(lineBuffer, "message(%d,%d,%d,%d,%d)\n", param1, param2, param3, param4, param5);

				break;
			}
		case 0x52:
		case 0x53:
			{
				byte param1;
				byte param2;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				if (param2) {
					byte param3;

					param3 = *(localScriptPtr + position);
					position++;

					if (param2 == 1) {
						if (opcode - 1 == 0x52) {
							sprintf(lineBuffer, "globalVar[%d] = var[%d]\n", param1, param3);
						} else if (opcode - 1 == 0x53) {
							sprintf(compareString1, "globalVar[%d]", param1);
							sprintf(compareString2, "var[%d]", param3);
						}
					} else if (param2 == 2) {
						if (opcode - 1 == 0x52) {
							sprintf(lineBuffer, "globalVar[%d] = globalVar[%d]\n", param1, param3);
						} else if (opcode - 1 == 0x53) {
							sprintf(compareString1, "globalVar[%d]", param1);
							sprintf(compareString2, "globalVar[%d]", param3);
						}
					} else {
						if (opcode - 1 == 0x52) {
							error("decompileScript: 0x52: param2 = %d", param2);
						} else if (opcode - 1 == 0x53) {
							error("decompileScript: 0x53: param2 = %d", param2);
						}
					}
				} else {
					int16 param3;

					param3 = READ_BE_UINT16(localScriptPtr + position);
					position += 2;

					if (opcode - 1 == 0x52) {
						sprintf(lineBuffer, "globalVar[%d] = %d\n", param1, param3);
					} else if (opcode - 1 == 0x53) {
						sprintf(compareString1, "globalVar[%d]", param1);
						sprintf(compareString2, "%d", param3);
					}
				}
				break;
			}
		case 0x59:
			{
				sprintf(lineBuffer, "comment: %s\n", localScriptPtr + position);

				position += strlen((char *)localScriptPtr + position);
				break;
			}
		case 0x5A:
			{
				byte param1;
				byte param2;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "freePartRang(%d,%d)\n", param1, param2);

				break;
			}
		case 0x5B:
			{
				sprintf(lineBuffer, "unloadAllMasks()\n");
				break;
			}
		case 0x65:
			{
				sprintf(lineBuffer, "setupTableUnk1()\n");
				break;
			}
		case 0x66:
			{
				byte param1;
				int16 param2;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				sprintf(lineBuffer, "tableUnk1[%d] = %d\n", param1, param2);

				break;
			}
		case 0x68:
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "setDefaultMenuBoxColor(%d)\n", param);

				break;
			}
		case 0x69:
			{
				sprintf(lineBuffer, "allowPlayerInput()\n");
				break;
			}
		case 0x6A:
			{
				sprintf(lineBuffer, "disallowPlayerInput()\n");
				break;
			}
		case OP_changeDataDisk:
			{
				byte newDisk;

				newDisk = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "changeDataDisk(%d)\n", newDisk);

				break;
			}
		case 0x6D:
			{
				sprintf(lineBuffer, "loadDat(%s)\n", localScriptPtr + position);

				position += strlen((char *)localScriptPtr + position) + 1;
				break;
			}
		case 0x6E:	// nop
			{
				sprintf(lineBuffer, "updateDat()\n");
				break;
			}
		case 0x6F:
			{
				sprintf(lineBuffer, "OP_6F() -> dat related\n");
				break;
			}
		case 0x70:
			{
				sprintf(lineBuffer, "stopSample()\n");
				break;
			}
		case 0x79:
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "disableSystemMenu(%d)\n", param);

				break;
			}
		case 0x77:
		case 0x78:
			{
				byte param1;
				byte param2;
				int16 param3;
				byte param4;
				int16 param5;
				int16 param6;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				param3 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param4 = *(localScriptPtr + position);
				position++;

				param5 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param6 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				if (opcode - 1 == 0x77) {
					sprintf(lineBuffer, "playSample(%d,%d,%d,%d,%d,%d)\n", param1, param2, param3, param4, param5, param6);
				else if (opcode - 1 == 0x78) {
					sprintf(lineBuffer, "OP_78(%d,%d,%d,%d,%d,%d)\n", param1, param2, param3, param4, param5, param6);
				}

				break;
			}
		case 0x7A:
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "OP_7A(%d)\n", param);

				break;
			}
		case 0x7B:	// OS only
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "OP_7B(%d)\n", param);

				break;
			}
		case 0x7F:	// OS only
			{
				byte param1;
				byte param2;
				byte param3;
				byte param4;
				int16 param5;
				int16 param6;
				int16 param7;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				param3 = *(localScriptPtr + position);
				position++;

				param4 = *(localScriptPtr + position);
				position++;

				param5 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param6 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param7 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				sprintf(lineBuffer, "OP_7F(%d,%d,%d,%d,%d,%d,%d)\n", param1, param2, param3, param4, param5, param6, param7);

				break;
			}
		case 0x80:	// OS only
			{
				byte param1;
				byte param2;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "OP_80(%d,%d)\n", param1, param2);

				break;
			}
		case 0x82:	// OS only
			{
				byte param1;
				byte param2;
				uint16 param3;
				uint16 param4;
				byte param5;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				param3 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param4 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param5 = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "OP_82(%d,%d,%d,%d,%d)\n", param1, param2, param3, param4, param5);

				break;
			}
		case 0x83:	// OS only
			{
				byte param1;
				byte param2;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "OP_83(%d,%d)\n", param1, param2);

				break;
			}
		case 0x89:	// OS only
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "if(%s!=%s) goto next label(%d)\n", compareString1, compareString2, param);

				break;
			}
		case 0x8B:	// OS only
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "OP_8B(%d)\n", param);

				break;
			}
		case 0x8C:	// OS only
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "OP_8C(%d)\n", param);

				break;
			}
		case 0x8D:	// OS only
			{
				int16 param1;
				int16 param2;
				int16 param3;
				int16 param4;
				int16 param5;
				int16 param6;
				int16 param7;
				int16 param8;

				param1 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param2 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param3 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param4 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param5 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param6 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param7 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				param8 = READ_BE_UINT16(localScriptPtr + position);
				position += 2;

				sprintf(compareString1, "obj[%d]", param1);
				sprintf(compareString2, "{%d,%d,%d,%d,%d,%d,%d}", param2, param3, param4, param5, param6, param7, param8);

				break;
			}
		case 0x8E:	// OS only
			{
				byte param1;

				param1 = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "ADDBG(%d,%s)\n", param1, localScriptPtr + position);

				position += strlen((char *)localScriptPtr + position);

				break;
			}
		case 0x8F:	// OS only
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "OP_8F(%d)\n", param);

				break;
			}
		case 0x90:	// OS only
			{
				byte param1;

				param1 = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "loadABS(%d,%s)\n", param1, localScriptPtr + position);

				position += strlen((char *)localScriptPtr + position);

				break;
			}
		case 0x91:	// OS only
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "OP_91(%d)\n", param);

				break;
			}
		case 0x9D:	// OS only
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "OP_9D(%d) -> flip img idx\n", param);

				break;
			}
		case 0x9E:	// OS only
			{
				byte param;

				param = *(localScriptPtr + position);
				position++;

				if (param) {
					byte param2;

					param2 = *(localScriptPtr + position);
					position++;

					sprintf(lineBuffer, "OP_9E(%d,%d)\n", param, param2);
				} else {
					int16 param2;

					param2 = READ_BE_UINT16(localScriptPtr + position);
					position += 2;

					sprintf(lineBuffer, "OP_9E(%d,%d)\n", param, param2);
				}

				break;
			}
		case 0xA0:	// OS only
			{
				byte param1;
				byte param2;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "OP_A0(%d,%d)\n", param1, param2);

				break;
			}
		case 0xA1:	// OS only
			{
				byte param1;
				byte param2;

				param1 = *(localScriptPtr + position);
				position++;

				param2 = *(localScriptPtr + position);
				position++;

				sprintf(lineBuffer, "OP_A1(%d,%d)\n", param1, param2);

				break;
			}
		default:
			{
				sprintf(lineBuffer, "Unsupported opcode %X in decompileScript\n\n", opcode - 1);
				position = scriptSize;
				break;
			}
		}

		// printf(lineBuffer);
		strcpy(decompileBuffer[decompileBufferPosition++], lineBuffer);

		exitScript = 0;
		if (position >= scriptSize) {
			exitScript = 1;
		}

	} while (!exitScript);
}

void dumpScript(char *dumpName) {
    Common::File fHandle;
	uint16 i;

	fHandle.open(dumpName, Common::File::kFileWriteMode);

	for (i = 0; i < decompileBufferPosition; i++) {
		fHandle.writeString(Common::String(decompileBuffer[i]));
	}

	fHandle.close();

	decompileBufferPosition = 0;
}

#endif

} // End of namespace Cine
