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

#include "common/config-manager.h"
#include "common/system.h"

#include "scumm/actor.h"
#include "scumm/charset.h"
#include "scumm/file.h"
#include "scumm/imuse/imuse.h"
#include "scumm/imuse_digi/dimuse.h"
#include "scumm/insane/insane.h"
#include "scumm/object.h"
#include "scumm/resource.h"
#include "scumm/scumm.h"
#include "scumm/scumm_v6.h"
#include "scumm/scumm_v7.h"
#include "scumm/smush/smush_player.h"
#include "scumm/sound.h"
#include "scumm/util.h"
#include "scumm/verbs.h"

namespace Scumm {

#define OPCODE(i, x)	_opcodes[i]._OPCODE(ScummEngine_v6, x)

void ScummEngine_v6::setupOpcodes() {
	/* 00 */
	OPCODE(0x00, o6_pushByte);
	OPCODE(0x01, o6_pushWord);
	OPCODE(0x02, o6_pushByteVar);
	OPCODE(0x03, o6_pushWordVar);
	/* 04 */
	OPCODE(0x06, o6_byteArrayRead);
	OPCODE(0x07, o6_wordArrayRead);
	/* 08 */
	OPCODE(0x0a, o6_byteArrayIndexedRead);
	OPCODE(0x0b, o6_wordArrayIndexedRead);
	/* 0C */
	OPCODE(0x0c, o6_dup);
	OPCODE(0x0d, o6_not);
	OPCODE(0x0e, o6_eq);
	OPCODE(0x0f, o6_neq);
	/* 10 */
	OPCODE(0x10, o6_gt);
	OPCODE(0x11, o6_lt);
	OPCODE(0x12, o6_le);
	OPCODE(0x13, o6_ge);
	/* 14 */
	OPCODE(0x14, o6_add);
	OPCODE(0x15, o6_sub);
	OPCODE(0x16, o6_mul);
	OPCODE(0x17, o6_div);
	/* 18 */
	OPCODE(0x18, o6_land);
	OPCODE(0x19, o6_lor);
	OPCODE(0x1a, o6_pop);
	/* 1C */
	/* 20 */
	/* 24 */
	/* 28 */
	/* 2C */
	/* 30 */
	/* 34 */
	/* 38 */
	/* 3C */
	/* 40 */
	OPCODE(0x42, o6_writeByteVar);
	OPCODE(0x43, o6_writeWordVar);
	/* 44 */
	OPCODE(0x46, o6_byteArrayWrite);
	OPCODE(0x47, o6_wordArrayWrite);
	/* 48 */
	OPCODE(0x4a, o6_byteArrayIndexedWrite);
	OPCODE(0x4b, o6_wordArrayIndexedWrite);
	/* 4C */
	OPCODE(0x4e, o6_byteVarInc);
	OPCODE(0x4f, o6_wordVarInc);
	/* 50 */
	OPCODE(0x52, o6_byteArrayInc);
	OPCODE(0x53, o6_wordArrayInc);
	/* 54 */
	OPCODE(0x56, o6_byteVarDec);
	OPCODE(0x57, o6_wordVarDec);
	/* 58 */
	OPCODE(0x5a, o6_byteArrayDec);
	OPCODE(0x5b, o6_wordArrayDec);
	/* 5C */
	OPCODE(0x5c, o6_if);
	OPCODE(0x5d, o6_ifNot);
	OPCODE(0x5e, o6_startScript);
	OPCODE(0x5f, o6_startScriptQuick);
	/* 60 */
	OPCODE(0x60, o6_startObject);
	OPCODE(0x61, o6_drawObject);
	OPCODE(0x62, o6_drawObjectAt);
	OPCODE(0x63, o6_drawBlastObject);
	/* 64 */
	OPCODE(0x64, o6_setBlastObjectWindow);
	OPCODE(0x65, o6_stopObjectCode);
	OPCODE(0x66, o6_stopObjectCode);
	OPCODE(0x67, o6_endCutscene);
	/* 68 */
	OPCODE(0x68, o6_cutscene);
	OPCODE(0x69, o6_stopMusic);
	OPCODE(0x6a, o6_freezeUnfreeze);
	OPCODE(0x6b, o6_cursorCommand);
	/* 6C */
	OPCODE(0x6c, o6_breakHere);
	OPCODE(0x6d, o6_ifClassOfIs);
	OPCODE(0x6e, o6_setClass);
	OPCODE(0x6f, o6_getState);
	/* 70 */
	OPCODE(0x70, o6_setState);
	OPCODE(0x71, o6_setOwner);
	OPCODE(0x72, o6_getOwner);
	OPCODE(0x73, o6_jump);
	/* 74 */
	OPCODE(0x74, o6_startSound);
	OPCODE(0x75, o6_stopSound);
	OPCODE(0x76, o6_startMusic);
	OPCODE(0x77, o6_stopObjectScript);
	/* 78 */
	OPCODE(0x78, o6_panCameraTo);
	OPCODE(0x79, o6_actorFollowCamera);
	OPCODE(0x7a, o6_setCameraAt);
	OPCODE(0x7b, o6_loadRoom);
	/* 7C */
	OPCODE(0x7c, o6_stopScript);
	OPCODE(0x7d, o6_walkActorToObj);
	OPCODE(0x7e, o6_walkActorTo);
	OPCODE(0x7f, o6_putActorAtXY);
	/* 80 */
	OPCODE(0x80, o6_putActorAtObject);
	OPCODE(0x81, o6_faceActor);
	OPCODE(0x82, o6_animateActor);
	OPCODE(0x83, o6_doSentence);
	/* 84 */
	OPCODE(0x84, o6_pickupObject);
	OPCODE(0x85, o6_loadRoomWithEgo);
	OPCODE(0x87, o6_getRandomNumber);
	/* 88 */
	OPCODE(0x88, o6_getRandomNumberRange);
	OPCODE(0x8a, o6_getActorMoving);
	OPCODE(0x8b, o6_isScriptRunning);
	/* 8C */
	OPCODE(0x8c, o6_getActorRoom);
	OPCODE(0x8d, o6_getObjectX);
	OPCODE(0x8e, o6_getObjectY);
	OPCODE(0x8f, o6_getObjectOldDir);
	/* 90 */
	OPCODE(0x90, o6_getActorWalkBox);
	OPCODE(0x91, o6_getActorCostume);
	OPCODE(0x92, o6_findInventory);
	OPCODE(0x93, o6_getInventoryCount);
	/* 94 */
	OPCODE(0x94, o6_getVerbFromXY);
	OPCODE(0x95, o6_beginOverride);
	OPCODE(0x96, o6_endOverride);
	OPCODE(0x97, o6_setObjectName);
	/* 98 */
	OPCODE(0x98, o6_isSoundRunning);
	OPCODE(0x99, o6_setBoxFlags);
	OPCODE(0x9a, o6_createBoxMatrix);
	OPCODE(0x9b, o6_resourceRoutines);
	/* 9C */
	OPCODE(0x9c, o6_roomOps);
	OPCODE(0x9d, o6_actorOps);
	OPCODE(0x9e, o6_verbOps);
	OPCODE(0x9f, o6_getActorFromXY);
	/* A0 */
	OPCODE(0xa0, o6_findObject);
	OPCODE(0xa1, o6_pseudoRoom);
	OPCODE(0xa2, o6_getActorElevation);
	OPCODE(0xa3, o6_getVerbEntrypoint);
	/* A4 */
	OPCODE(0xa4, o6_arrayOps);
	OPCODE(0xa5, o6_saveRestoreVerbs);
	OPCODE(0xa6, o6_drawBox);
	OPCODE(0xa7, o6_pop);
	/* A8 */
	OPCODE(0xa8, o6_getActorWidth);
	OPCODE(0xa9, o6_wait);
	OPCODE(0xaa, o6_getActorScaleX);
	OPCODE(0xab, o6_getActorAnimCounter);
	/* AC */
	OPCODE(0xac, o6_soundKludge);
	OPCODE(0xad, o6_isAnyOf);
	OPCODE(0xae, o6_systemOps);
	OPCODE(0xaf, o6_isActorInBox);
	/* B0 */
	OPCODE(0xb0, o6_delay);
	OPCODE(0xb1, o6_delaySeconds);
	OPCODE(0xb2, o6_delayMinutes);
	OPCODE(0xb3, o6_stopSentence);
	/* B4 */
	OPCODE(0xb4, o6_printLine);
	OPCODE(0xb5, o6_printText);
	OPCODE(0xb6, o6_printDebug);
	OPCODE(0xb7, o6_printSystem);
	/* B8 */
	OPCODE(0xb8, o6_printActor);
	OPCODE(0xb9, o6_printEgo);
	OPCODE(0xba, o6_talkActor);
	OPCODE(0xbb, o6_talkEgo);
	/* BC */
	OPCODE(0xbc, o6_dimArray);
	OPCODE(0xbd, o6_dummy);
	OPCODE(0xbe, o6_startObjectQuick);
	OPCODE(0xbf, o6_startScriptQuick2);
	/* C0 */
	OPCODE(0xc0, o6_dim2dimArray);
	/* C4 */
	OPCODE(0xc4, o6_abs);
	OPCODE(0xc5, o6_distObjectObject);
	OPCODE(0xc6, o6_distObjectPt);
	OPCODE(0xc7, o6_distPtPt);
	/* C8 */
	OPCODE(0xc8, o6_kernelGetFunctions);
	OPCODE(0xc9, o6_kernelSetFunctions);
	OPCODE(0xca, o6_delayFrames);
	OPCODE(0xcb, o6_pickOneOf);
	/* CC */
	OPCODE(0xcc, o6_pickOneOfDefault);
	OPCODE(0xcd, o6_stampObject);
	/* D0 */
	OPCODE(0xd0, o6_getDateTime);
	OPCODE(0xd1, o6_stopTalking);
	OPCODE(0xd2, o6_getAnimateVariable);
	/* D4 */
	OPCODE(0xd4, o6_shuffle);
	OPCODE(0xd5, o6_jumpToScript);
	OPCODE(0xd6, o6_band);
	OPCODE(0xd7, o6_bor);
	/* D8 */
	OPCODE(0xd8, o6_isRoomScriptRunning);
	/* DC */
	OPCODE(0xdd, o6_findAllObjects);
	/* E0 */
	OPCODE(0xe1, o6_getPixel);
	OPCODE(0xe3, o6_pickVarRandom);
	/* E4 */
	OPCODE(0xe4, o6_setBoxSet);
	/* E8 */
	/* EC */
	OPCODE(0xec, o6_getActorLayer);
	OPCODE(0xed, o6_getObjectNewDir);
}

int ScummEngine_v6::popRoomAndObj(int *room) {
	int obj;

	if (_game.version >= 7) {
		obj = pop();
		*room = getObjectRoom(obj);
	} else {
		*room = pop();
		obj = pop();
	}

	return obj;
}

byte *ScummEngine_v6::defineArray(int array, int type, int dim2, int dim1) {
	int id;
	int size;
	ArrayHeader *ah;

	assert(0 <= type && type <= 5);


	if (_game.heversion >= 61) {
		if (type == kBitArray || type == kNibbleArray)
			type = kByteArray;
	} else {
		// NOTE: The following code turns all arrays except string arrays into
		// integer arrays. There seems to be no reason for this, and it wastes
		// space. However, we can't just remove this either, as that would
		// break savegame compatibility. So do not touch this unless you are
		// also adding code which updates old savegames, too. And of course
		// readArray() and writeArray() would have to be updated, too...
		if (type != kStringArray)
			type = kIntArray;
	}

	nukeArray(array);

	id = findFreeArrayId();

	if (_game.version == 8) {
		if (array & 0x40000000) {
		}

		if (array & 0x80000000) {
			error("Can't define bit variable as array pointer");
		}

		size = (type == kIntArray) ? 4 : 1;
	} else {
		if (array & 0x4000) {
		}

		if (array & 0x8000) {
			error("Can't define bit variable as array pointer");
		}

		size = (type == kIntArray) ? 2 : 1;
	}

	writeVar(array, id);

	size *= dim2 + 1;
	size *= dim1 + 1;

	ah = (ArrayHeader *)_res->createResource(rtString, id, size + sizeof(ArrayHeader));

	ah->type = TO_LE_16(type);
	ah->dim1 = TO_LE_16(dim1 + 1);
	ah->dim2 = TO_LE_16(dim2 + 1);

	return ah->data;
}

void ScummEngine_v6::nukeArray(int a) {
	int data;

	data = readVar(a);

	if (_game.heversion >= 80)
		data &= ~0x33539000;

	if (data)
		_res->nukeResource(rtString, data);
	if (_game.heversion >= 60)
		_arraySlot[data] = 0;

	writeVar(a, 0);
}

int ScummEngine_v6::findFreeArrayId() {
	const ResourceManager::ResTypeData &rtd = _res->_types[rtString];
	int i;

	for (i = 1; i < _numArray; i++) {
		if (!rtd[i]._address)
			return i;
	}
	error("Out of array pointers, %d max", _numArray);
	return -1;
}

#define SWAP16(x)  x = SWAP_BYTES_16(x)

ScummEngine_v6::ArrayHeader *ScummEngine_v6::getArray(int array) {
	ArrayHeader *ah = (ArrayHeader *)getResourceAddress(rtString, readVar(array));
	if (!ah)
		return 0;

	if (_game.heversion == 0) {
		// Workaround for a long standing bug where we saved array headers in native
		// endianness, instead of a fixed endianness. We now always store the
		// dimensions in little endian byte order. But to stay compatible with older
		// savegames, we try to detect savegames which were created on a big endian
		// system and convert them to the proper little endian format on the fly.
		if ((FROM_LE_16(ah->dim1) & 0xF000) || (FROM_LE_16(ah->dim2) & 0xF000) || (FROM_LE_16(ah->type) & 0xFF00)) {
			SWAP16(ah->dim1);
			SWAP16(ah->dim2);
			SWAP16(ah->type);
		}
	}

	return ah;
}

int ScummEngine_v6::readArray(int array, int idx, int base) {
	ArrayHeader *ah = getArray(array);

	if (!ah)
		error("readArray: invalid array %d (%d)", array, readVar(array));

	// WORKAROUND bug #645711. This is clearly a script bug, as this script
	// excerpt shows nicely:
	// ...
	// [03A7] (5D)         if (isAnyOf(array-447[localvar13][localvar14],[0,4])) {
	// [03BD] (5D)           if ((localvar13 != -1) && (localvar14 != -1)) {
	// [03CF] (B6)             printDebug.begin()
	// ...
	// So it checks for invalid array indices only *after* using them to access
	// the array. Ouch.
	if (_game.id == GID_FT && array == 447 && _currentRoom == 95 && vm.slot[_currentScript].number == 2010 && idx == -1 && base == -1) {
		return 0;
	}

	const int offset = base + idx * FROM_LE_16(ah->dim1);

	if (offset < 0 || offset >= FROM_LE_16(ah->dim1) * FROM_LE_16(ah->dim2)) {
		error("readArray: array %d out of bounds: [%d,%d] exceeds [%d,%d]",
			array, base, idx, FROM_LE_16(ah->dim1), FROM_LE_16(ah->dim2));
	}

	int val;
	if (FROM_LE_16(ah->type) != kIntArray) {
		val = ah->data[offset];
	} else if (_game.version == 8) {
		val = (int32)READ_LE_UINT32(ah->data + offset * 4);
	} else {
		val = (int16)READ_LE_UINT16(ah->data + offset * 2);
	}
	return val;
}

void ScummEngine_v6::writeArray(int array, int idx, int base, int value) {
	ArrayHeader *ah = getArray(array);
	if (!ah)
		return;

	const int offset = base + idx * FROM_LE_16(ah->dim1);

	if (offset < 0 || offset >= FROM_LE_16(ah->dim1) * FROM_LE_16(ah->dim2)) {
		error("writeArray: array %d out of bounds: [%d,%d] exceeds [%d,%d]",
			array, base, idx, FROM_LE_16(ah->dim1), FROM_LE_16(ah->dim2));
	}

	if (FROM_LE_16(ah->type) != kIntArray) {
		ah->data[offset] = value;
	} else if (_game.version == 8) {
		WRITE_LE_UINT32(ah->data + offset * 4, value);
	} else {
		WRITE_LE_UINT16(ah->data + offset * 2, value);
	}
}

void ScummEngine_v6::readArrayFromIndexFile() {
	int num;
	int a, b, c;

	while ((num = _fileHandle->readUint16LE()) != 0) {
		a = _fileHandle->readUint16LE();
		b = _fileHandle->readUint16LE();
		c = _fileHandle->readUint16LE();
		if (c == kBitArray)
			defineArray(num, kBitArray, a, b);
		else
			defineArray(num, kIntArray, a, b);
	}
}

int ScummEngine_v6::getStackList(int *args, uint maxnum) {
	uint num, i;

	for (i = 0; i < maxnum; i++)
		args[i] = 0;

	num = pop();

	if (num > maxnum)
		error("Too many items %d in stack list, max %d", num, maxnum);

	i = num;
	while (i--) {
		args[i] = pop();
	}

	return num;
}

void ScummEngine_v6::o6_pushByte() {
	push(fetchScriptByte());
}

void ScummEngine_v6::o6_pushWord() {
	push(fetchScriptWordSigned());
}

void ScummEngine_v6::o6_pushByteVar() {
	push(readVar(fetchScriptByte()));
}

void ScummEngine_v6::o6_pushWordVar() {
	push(readVar(fetchScriptWord()));
}

void ScummEngine_v6::o6_byteArrayRead() {
	int base = pop();
	push(readArray(fetchScriptByte(), 0, base));
}

void ScummEngine_v6::o6_wordArrayRead() {
	int base = pop();
	push(readArray(fetchScriptWord(), 0, base));
}

void ScummEngine_v6::o6_byteArrayIndexedRead() {
	int base = pop();
	int idx = pop();
	push(readArray(fetchScriptByte(), idx, base));
}

void ScummEngine_v6::o6_wordArrayIndexedRead() {
	int base = pop();
	int idx = pop();
	push(readArray(fetchScriptWord(), idx, base));
}

void ScummEngine_v6::o6_dup() {
	int a = pop();
	push(a);
	push(a);
}

void ScummEngine_v6::o6_not() {
	push(pop() == 0);
}

void ScummEngine_v6::o6_eq() {
	int a = pop();
	int b = pop();

	// WORKAROUND: Forces the game version string set via script 1 to be used in both Macintosh and Windows versions,
	// when checking for save game compatibility. Allows saved games to be shared between Macintosh and Windows versions.
	// The scripts check VAR_PLATFORM (b) against the value (2) of the Macintosh platform (a).
	if (_game.id == GID_BASEBALL2001 && (vm.slot[_currentScript].number == 291 || vm.slot[_currentScript].number == 292) &&
		a == 2 && b == 1) {
		push(1);
	} else {
		push(a == b);
	}
}

void ScummEngine_v6::o6_neq() {
	push(pop() != pop());
}

void ScummEngine_v6::o6_gt() {
	int a = pop();
	push(pop() > a);
}

void ScummEngine_v6::o6_lt() {
	int a = pop();
	push(pop() < a);
}

void ScummEngine_v6::o6_le() {
	int a = pop();
	push(pop() <= a);
}

void ScummEngine_v6::o6_ge() {
	int a = pop();
	push(pop() >= a);
}

void ScummEngine_v6::o6_add() {
	int a = pop();
	push(pop() + a);
}

void ScummEngine_v6::o6_sub() {
	int a = pop();
	push(pop() - a);
}

void ScummEngine_v6::o6_mul() {
	int a = pop();
	push(pop() * a);
}

void ScummEngine_v6::o6_div() {
	int a = pop();
	if (a == 0)
		error("division by zero");
	push(pop() / a);
}

void ScummEngine_v6::o6_land() {
	int a = pop();
	push(pop() && a);
}

void ScummEngine_v6::o6_lor() {
	int a = pop();
	push(pop() || a);
}

void ScummEngine_v6::o6_bor() {
	int a = pop();
	push(pop() | a);
}

void ScummEngine_v6::o6_band() {
	int a = pop();
	push(pop() & a);
}

void ScummEngine_v6::o6_pop() {
	pop();
}

void ScummEngine_v6::o6_writeByteVar() {
	writeVar(fetchScriptByte(), pop());
}

void ScummEngine_v6::o6_writeWordVar() {
	writeVar(fetchScriptWord(), pop());
}

void ScummEngine_v6::o6_byteArrayWrite() {
	int a = pop();
	writeArray(fetchScriptByte(), 0, pop(), a);
}

void ScummEngine_v6::o6_wordArrayWrite() {
	int a = pop();
	writeArray(fetchScriptWord(), 0, pop(), a);
}

void ScummEngine_v6::o6_byteArrayIndexedWrite() {
	int val = pop();
	int base = pop();
	writeArray(fetchScriptByte(), pop(), base, val);
}

void ScummEngine_v6::o6_wordArrayIndexedWrite() {
	int val = pop();
	int base = pop();
	writeArray(fetchScriptWord(), pop(), base, val);
}

void ScummEngine_v6::o6_byteVarInc() {
	int var = fetchScriptByte();
	writeVar(var, readVar(var) + 1);
}

void ScummEngine_v6::o6_wordVarInc() {
	int var = fetchScriptWord();
	writeVar(var, readVar(var) + 1);
}

void ScummEngine_v6::o6_byteArrayInc() {
	int var = fetchScriptByte();
	int base = pop();
	writeArray(var, 0, base, readArray(var, 0, base) + 1);
}

void ScummEngine_v6::o6_wordArrayInc() {
	int var = fetchScriptWord();
	int base = pop();
	writeArray(var, 0, base, readArray(var, 0, base) + 1);
}

void ScummEngine_v6::o6_byteVarDec() {
	int var = fetchScriptByte();
	writeVar(var, readVar(var) - 1);
}

void ScummEngine_v6::o6_wordVarDec() {
	int var = fetchScriptWord();
	writeVar(var, readVar(var) - 1);
}

void ScummEngine_v6::o6_byteArrayDec() {
	int var = fetchScriptByte();
	int base = pop();
	writeArray(var, 0, base, readArray(var, 0, base) - 1);
}

void ScummEngine_v6::o6_wordArrayDec() {
	int var = fetchScriptWord();
	int base = pop();
	writeArray(var, 0, base, readArray(var, 0, base) - 1);
}

void ScummEngine_v6::o6_if() {
	if (pop())
		o6_jump();
	else
		fetchScriptWord();
}

void ScummEngine_v6::o6_ifNot() {
	if (!pop())
		o6_jump();
	else
		fetchScriptWord();
}

void ScummEngine_v6::o6_jump() {
	int offset = fetchScriptWordSigned();

	// WORKAROUND bug #2826144: Talking to the guard at the bigfoot party, after
	// he's let you inside, will cause the game to hang, if you end the conversation.
	// This is a script bug, due to a missing jump in one segment of the script.
	if (_game.id == GID_SAMNMAX && vm.slot[_currentScript].number == 101 && readVar(0x8000 + 97) == 1 && offset == 1) {
		offset = -18;
	}

	_scriptPointer += offset;
}

void ScummEngine_v6::o6_startScript() {
	int args[25];
	int script, flags;

	getStackList(args, ARRAYSIZE(args));
	script = pop();
	flags = pop();

	// WORKAROUND bug #556558: At Dino Bungee National Memorial, the buttons for
	// the Wally and Rex dinosaurs will always restart their speech, instead of
	// stopping and starting their speech. This was a script bug in the original
	// game.
	if (_game.id == GID_SAMNMAX && _roomResource == 59 &&
		vm.slot[_currentScript].number == 201 && script == 48) {
		o6_breakHere();
	}

	// WORKAROUND bug #903223: In Puerto Pollo, if you have Guybrush examine
	// the church clock, he'll read out the current time. Nice touch, only that
	// it sounds crap in the german version (and maybe others, too). It seems
	// the original engine of the german version played just a simple fixed
	// text in this spot, for the above reason. Since the data files are
	// unchanged, it must have been an engine hack job. No idea how they did
	// it exactly, but this here is how we do it :-)
	if (_game.id == GID_CMI && script == 204 &&
		_currentRoom == 15 && vm.slot[_currentScript].number == 421 &&
		_language == Common::DE_DEU) {

		_actorToPrintStrFor = 1;
		_string[0].loadDefault();
		actorTalk((const byte *)"/VDSO325/Whoa! Look at the time. Gotta scoot.");

		return;
	}

	// WORKAROUND bug #1878514: When turning pages in the recipe book
	// (found on Blood Island), there is a brief moment where it displays
	// text from two different pages at the same time.
	//
	// The content of the books is drawing (in an endless loop) by local
	// script 2007. Changing the page is handled by script 2006, which
	// first stops script 2007; then switches the page; then restarts
	// script 2007. But it fails to clear the blast texts beforehand.
	// Hence, the next time blast text is drawn, both the old one (from
	// the old instance of script 2007) and the new text (from the new
	// instance) are briefly drawn simultaneously.
	//
	// This looks like a script bug to me (a missing call to clearTextQueue).
	// But this could also hint at a subtle bug in ScummVM; we should check
	// whether this bug occurs with the original engine or not.
	if (_game.id == GID_CMI && script == 2007 &&
		_currentRoom == 62 && vm.slot[_currentScript].number == 2006) {

		removeBlastTexts();
	}

	runScript(script, (flags & 1) != 0, (flags & 2) != 0, args);
}

void ScummEngine_v6::o6_jumpToScript() {
	int args[25];
	int script, flags;

	getStackList(args, ARRAYSIZE(args));
	script = pop();
	flags = pop();
	stopObjectCode();
	runScript(script, (flags & 1) != 0, (flags & 2) != 0, args);
}

void ScummEngine_v6::o6_startScriptQuick() {
	int args[25];
	int script;
	getStackList(args, ARRAYSIZE(args));
	script = pop();
	runScript(script, 0, 0, args);
}

void ScummEngine_v6::o6_startScriptQuick2() {
	int args[25];
	int script;
	getStackList(args, ARRAYSIZE(args));
	script = pop();
	runScript(script, 0, 1, args);
}

void ScummEngine_v6::o6_startObject() {
	int args[25];
	int script, entryp;
	int flags;
	getStackList(args, ARRAYSIZE(args));
	entryp = pop();
	script = pop();
	flags = pop();
	runObjectScript(script, entryp, (flags & 1) != 0, (flags & 2) != 0, args);
}

void ScummEngine_v6::o6_startObjectQuick() {
	int args[25];
	int script, entryp;
	getStackList(args, ARRAYSIZE(args));
	entryp = pop();
	script = pop();
	runObjectScript(script, entryp, 0, 1, args);
}

void ScummEngine_v6::o6_drawObject() {
	int state = pop();
	int obj = pop();

	// This is based on disassembly
	if (state == 0)
		state = 1;

	setObjectState(obj, state, -1, -1);
}

void ScummEngine_v6::o6_drawObjectAt() {
	int y = pop();
	int x = pop();
	int obj = pop();

	// WORKAROUND bug #1846746 : Adjust x and y position of
	// objects in credits sequence, to match other ports
	if (_game.id == GID_PUTTMOON && _game.platform == Common::kPlatform3DO &&
		_roomResource == 38 && vm.slot[_currentScript].number == 206) {
		x = y = -1;
	}

	setObjectState(obj, 1, x, y);
}

void ScummEngine_v6::o6_stopObjectCode() {
	stopObjectCode();
}

void ScummEngine_v6::o6_endCutscene() {
	endCutscene();
}

void ScummEngine_v6::o6_cutscene() {
	int args[25];
	getStackList(args, ARRAYSIZE(args));
	beginCutscene(args);
}

void ScummEngine_v6::o6_stopMusic() {
	_sound->stopAllSounds();
}

void ScummEngine_v6::o6_freezeUnfreeze() {
	int a = pop();

	if (a)
		freezeScripts(a);
	else
		unfreezeScripts();
}

void ScummEngine_v6::o6_cursorCommand() {
	int a, i;
	int args[16];

	byte subOp = fetchScriptByte();

	switch (subOp) {
	case 0x90:		// SO_CURSOR_ON Turn cursor on
		_cursor.state = 1;
		verbMouseOver(0);
		break;
	case 0x91:		// SO_CURSOR_OFF Turn cursor off
		_cursor.state = 0;
		verbMouseOver(0);
		break;
	case 0x92:		// SO_USERPUT_ON
		_userPut = 1;
		break;
	case 0x93:		// SO_USERPUT_OFF
		_userPut = 0;
		break;
	case 0x94:		// SO_CURSOR_SOFT_ON Turn soft cursor on
		_cursor.state++;
		if (_cursor.state > 1)
			error("Cursor state greater than 1 in script");
		verbMouseOver(0);
		break;
	case 0x95:		// SO_CURSOR_SOFT_OFF Turn soft cursor off
		_cursor.state--;
		verbMouseOver(0);
		break;
	case 0x96:		// SO_USERPUT_SOFT_ON
		_userPut++;
		break;
	case 0x97:		// SO_USERPUT_SOFT_OFF
		_userPut--;
		break;
	case 0x99:		// SO_CURSOR_IMAGE Set cursor image
		{
			int room, obj;
			if (_game.heversion >= 70) {
				obj = pop();
				room = getObjectRoom(obj);
			} else {
				obj = popRoomAndObj(&room);
			}
			setCursorFromImg(obj, room, 1);
			break;
		}
	case 0x9A:		// SO_CURSOR_HOTSPOT Set cursor hotspot
		a = pop();
		setCursorHotspot(pop(), a);
		updateCursor();
		break;
	case 0x9C:		// SO_CHARSET_SET
		initCharset(pop());
		break;
	case 0x9D:		// SO_CHARSET_COLOR
		getStackList(args, ARRAYSIZE(args));
		for (i = 0; i < 16; i++)
			_charsetColorMap[i] = _charsetData[_string[1]._default.charset][i] = (unsigned char)args[i];
		break;
	case 0xD6:		// SO_CURSOR_TRANSPARENT Set cursor transparent color
		setCursorTransparency(pop());
		break;
	default:
		error("o6_cursorCommand: default case %x", subOp);
	}

	VAR(VAR_CURSORSTATE) = _cursor.state;
	VAR(VAR_USERPUT) = _userPut;
}

void ScummEngine_v6::o6_breakHere() {
	updateScriptPtr();
	_currentScript = 0xFF;
}

void ScummEngine_v6::o6_ifClassOfIs() {
	int args[16];
	int num, obj, cls;
	bool b;
	int cond = 1;

	num = getStackList(args, ARRAYSIZE(args));
	obj = pop();

	if (_game.heversion >= 80 && num == 0) {
		push(_classData[obj]);
		return;
	}

	while (--num >= 0) {
		cls = args[num];
		b = getClass(obj, cls);
		if ((cls & 0x80 && !b) || (!(cls & 0x80) && b))
			cond = 0;
	}
	push(cond);
}

void ScummEngine_v6::o6_setClass() {
	int args[16];
	int num, obj, cls;

	num = getStackList(args, ARRAYSIZE(args));
	obj = pop();

	while (--num >= 0) {
		cls = args[num];
		if (cls == 0)
			_classData[num] = 0;
		else if (cls & 0x80)
			putClass(obj, cls, 1);
		else
			putClass(obj, cls, 0);
	}
}

void ScummEngine_v6::o6_getState() {
	push(getState(pop()));
}

void ScummEngine_v6::o6_setState() {
	int state = pop();
	int obj = pop();

	putState(obj, state);
	markObjectRectAsDirty(obj);
	if (_bgNeedsRedraw)
		clearDrawObjectQueue();
}

void ScummEngine_v6::o6_setOwner() {
	int owner = pop();
	int obj = pop();
	setOwnerOf(obj, owner);
}

void ScummEngine_v6::o6_getOwner() {
	push(getOwner(pop()));
}

void ScummEngine_v6::o6_startSound() {
	int offset = 0;

	// In Fatty Bear's Birthday Surprise the piano uses offsets 1 - 23 to
	// indicate which note to play, but only when using the standard piano
	// sound. See also o60_soundOps()
	if (_game.heversion >= 60 && (_game.id != GID_PUTTDEMO))
		offset = pop();

#ifdef ENABLE_SCUMM_7_8
	if (_game.version >= 7)
		_imuseDigital->startSfx(pop(), 64);
	else
#endif
		_sound->addSoundToQueue(pop(), offset);
}

void ScummEngine_v6::o6_stopSound() {
	_sound->stopSound(pop());
}

void ScummEngine_v6::o6_startMusic() {
	if (_game.version >= 7)
		error("o6_startMusic() It shouldn't be called here for imuse digital");

	_sound->addSoundToQueue(pop());
}

void ScummEngine_v6::o6_stopObjectScript() {
	stopObjectScript(pop());
}

void ScummEngine_v6::o6_panCameraTo() {
	if (_game.version >= 7) {
		int y = pop();
		int x = pop();
		panCameraTo(x, y);
	} else {
		panCameraTo(pop(), 0);
	}
}

void ScummEngine_v6::o6_actorFollowCamera() {
	if (_game.version >= 7)
		setCameraFollows(derefActor(pop(), "actorFollowCamera"));
	else
		actorFollowCamera(pop());
}

void ScummEngine_v6::o6_setCameraAt() {
	if (_game.version >= 7) {
		int x, y;

		camera._follows = 0;
		VAR(VAR_CAMERA_FOLLOWED_ACTOR) = 0;

		y = pop();
		x = pop();

		setCameraAt(x, y);
	} else {
		setCameraAtEx(pop());
	}
}

void ScummEngine_v6::o6_loadRoom() {
	int room = pop();
	startScene(room, 0, 0);
	if (_game.heversion >= 61) {
		setCameraAt(camera._cur.x, 0);
	}
	_fullRedraw = true;
}

void ScummEngine_v6::o6_stopScript() {
	int script = pop();
	if (script == 0)
		stopObjectCode();
	else
		stopScript(script);
}

void ScummEngine_v6::o6_walkActorToObj() {
	int act, obj, dist;
	Actor *a, *a2;
	int x, y;

	dist = pop();
	obj = pop();
	act = pop();
	a = derefActor(act, "o6_walkActorToObj");

	if (obj >= _numActors) {
		int wio = whereIsObject(obj);

		if (wio != WIO_FLOBJECT && wio != WIO_ROOM)
			return;

		int dir;
		getObjectXYPos(obj, x, y, dir);
		a->startWalkActor(x, y, dir);
	} else {
		a2 = derefActorSafe(obj, "o6_walkActorToObj(2)");
		if (_game.id == GID_SAMNMAX && a2 == 0) {
			// WORKAROUND bug #742676 SAM: Fish Farm. Note quite sure why it
			// happens, whether it's normal or due to a bug in the ScummVM code.
			debug(0, "o6_walkActorToObj: invalid actor %d", obj);
			return;
		}
		if (!a->isInCurrentRoom() || !a2->isInCurrentRoom())
			return;
		if (dist == 0) {
			dist = a2->_scalex * a2->_width / 0xFF;
			dist += dist / 2;
		}
		x = a2->getPos().x;
		y = a2->getPos().y;
		if (x < a->getPos().x)
			x += dist;
		else
			x -= dist;
		a->startWalkActor(x, y, -1);
	}
}

void ScummEngine_v6::o6_walkActorTo() {
	int x, y;
	y = pop();
	x = pop();
	Actor *a = derefActor(pop(), "o6_walkActorTo");
	a->startWalkActor(x, y, -1);
}

void ScummEngine_v6::o6_putActorAtXY() {
	int room, x, y, act;
	Actor *a;

	room = pop();
	y = pop();
	x = pop();
	act = pop();
	a = derefActor(act, "o6_putActorAtXY");

	if (room == 0xFF || room == 0x7FFFFFFF) {
		room = a->_room;
	} else {
		if (a->_visible && _currentRoom != room && getTalkingActor() == a->_number) {
			stopTalk();
		}
		if (room != 0)
			a->_room = room;
	}
	a->putActor(x, y, room);
}


void ScummEngine_v6::o6_putActorAtObject() {
	int room, obj, x, y;
	Actor *a;

	obj = popRoomAndObj(&room);

	a = derefActor(pop(), "o6_putActorAtObject");
	if (whereIsObject(obj) != WIO_NOT_FOUND) {
		getObjectXYPos(obj, x, y);
	} else {
		x = 160;
		y = 120;
	}
	if (room == 0xFF)
		room = a->_room;
	a->putActor(x, y, room);
}

void ScummEngine_v6::o6_faceActor() {
	int obj = pop();
	Actor *a = derefActor(pop(), "o6_faceActor");
	a->faceToObject(obj);
}

void ScummEngine_v6::o6_animateActor() {
	int anim = pop();
	int act = pop();
	if (_game.id == GID_TENTACLE && _roomResource == 57 &&
		vm.slot[_currentScript].number == 19 && act == 593) {
		// WORKAROUND bug #743363: This very odd case (animateActor(593,250))
		// occurs in DOTT, in the cutscene after George cuts down the "cherry
		// tree" and the tree Laverne is trapped in vanishes...
		// Not sure if this means animateActor somehow also must work for objects
		// (593 is the time machine in room 57), or if this is simply a script bug.
		act = 6;
	}
	if (_game.id == GID_SAMNMAX && _roomResource == 35 &&
		vm.slot[_currentScript].number == 202 && act == 4 && anim == 14) {
		// WORKAROUND bug #1223621 (Animation glitch at World of Fish).
		// Before starting animation 14 of the fisherman, make sure he isn't
		// talking anymore. This appears to be a bug in the original game as well.
		if (getTalkingActor() == 4) {
			stopTalk();
		}
	}
	Actor *a = derefActor(act, "o6_animateActor");
	a->animateActor(anim);
}

void ScummEngine_v6::o6_doSentence() {
	int verb, objectA, objectB;

	objectB = pop();
	if (_game.version < 8)
		pop();	// dummy pop (in Sam&Max, seems to be always 0 or 130)
	objectA = pop();
	verb = pop();

	doSentence(verb, objectA, objectB);
}

void ScummEngine_v6::o6_pickupObject() {
	int obj, room;
	int i;

	obj = popRoomAndObj(&room);
	if (room == 0)
		room = _roomResource;

	for (i = 0; i < _numInventory; i++) {
		if (_inventory[i] == (uint16)obj) {
			putOwner(obj, VAR(VAR_EGO));
			runInventoryScript(obj);
			return;
		}
	}

	addObjectToInventory(obj, room);
	putOwner(obj, VAR(VAR_EGO));
	putClass(obj, kObjectClassUntouchable, 1);
	putState(obj, 1);
	markObjectRectAsDirty(obj);
	clearDrawObjectQueue();
	runInventoryScript(obj);
}

void ScummEngine_v6::o6_loadRoomWithEgo() {
	Actor *a;
	int obj, room, x, y;

	y = pop();
	x = pop();

	obj = popRoomAndObj(&room);

	a = derefActor(VAR(VAR_EGO), "o6_loadRoomWithEgo");
	a->putActor(0, 0, room);
	_egoPositioned = false;

	VAR(VAR_WALKTO_OBJ) = obj;
	startScene(a->_room, a, obj);
	VAR(VAR_WALKTO_OBJ) = 0;

	if (_game.version == 6) {
		camera._cur.x = camera._dest.x = a->getPos().x;
		setCameraFollows(a, (_game.heversion >= 60));
	}

	_fullRedraw = true;

	if (x != -1 && x != 0x7FFFFFFF) {
		a->startWalkActor(x, y, -1);
	}
}

void ScummEngine_v6::o6_getRandomNumber() {
	int rnd;
	rnd = _rnd.getRandomNumber(ABS(pop()));
	if (VAR_RANDOM_NR != 0xFF)
		VAR(VAR_RANDOM_NR) = rnd;
	push(rnd);
}

void ScummEngine_v6::o6_getRandomNumberRange() {
	int max = pop();
	int min = pop();
	int rnd = _rnd.getRandomNumberRng(min, max);
	if (VAR_RANDOM_NR != 0xFF)
		VAR(VAR_RANDOM_NR) = rnd;
	push(rnd);
}

void ScummEngine_v6::o6_isScriptRunning() {
	push(isScriptRunning(pop()));
}

void ScummEngine_v6::o6_isRoomScriptRunning() {
	push(isRoomScriptRunning(pop()));
}

void ScummEngine_v6::o6_getActorMoving() {
	Actor *a = derefActor(pop(), "o6_getActorMoving");
	push(a->_moving);
}

void ScummEngine_v6::o6_getActorRoom() {
	int act = pop();

	if (act == 0) {
		// This case occurs at the very least in COMI. That's because in COMI's script 28,
		// there is a check which looks as follows:
		//   if (((VAR_TALK_ACTOR != 0) && (VAR_HAVE_MSG == 1)) &&
		//        (getActorRoom(VAR_TALK_ACTOR) == VAR_ROOM))
		// Due to the way this is represented in bytecode, the engine cannot
		// short circuit. Hence, even though this would be perfectly fine code
		// in C/C++, here it can (and does) lead to getActorRoom(0) being
		// invoked. We silently ignore this.
		push(0);
		return;
	}

	if (act == 255) {
		// This case also occurs in COMI...
		push(0);
		return;
	}

	Actor *a = derefActor(act, "o6_getActorRoom");
	push(a->_room);
}

void ScummEngine_v6::o6_getActorWalkBox() {
	Actor *a = derefActor(pop(), "o6_getActorWalkBox");
	push(a->_ignoreBoxes ? 0 : a->_walkbox);
}

void ScummEngine_v6::o6_getActorCostume() {
	Actor *a = derefActor(pop(), "o6_getActorCostume");
	push(a->_costume);
}

void ScummEngine_v6::o6_getActorElevation() {
	Actor *a = derefActor(pop(), "o6_getActorElevation");
	push(a->getElevation());
}

void ScummEngine_v6::o6_getActorWidth() {
	Actor *a = derefActor(pop(), "o6_getActorWidth");
	push(a->_width);
}

void ScummEngine_v6::o6_getActorScaleX() {
	Actor *a = derefActor(pop(), "o6_getActorScaleX");
	push(a->_scalex);
}

void ScummEngine_v6::o6_getActorAnimCounter() {
	Actor *a = derefActor(pop(), "o6_getActorAnimCounter");
	push(a->_cost.animCounter);
}

void ScummEngine_v6::o6_getAnimateVariable() {
	int var = pop();
	Actor *a = derefActor(pop(), "o6_getAnimateVariable");
	push(a->getAnimVar(var));
}

void ScummEngine_v6::o6_isActorInBox() {
	int box = pop();
	Actor *a = derefActor(pop(), "o6_isActorInBox");
	push(checkXYInBoxBounds(box, a->getPos().x, a->getPos().y));
}

void ScummEngine_v6::o6_getActorLayer() {
	Actor *a = derefActor(pop(), "getActorLayer");
	push(a->_layer);
}

void ScummEngine_v6::o6_getObjectX() {
	push(getObjX(pop()));
}

void ScummEngine_v6::o6_getObjectY() {
	push(getObjY(pop()));
}

void ScummEngine_v6::o6_getObjectOldDir() {
	push(getObjOldDir(pop()));
}

void ScummEngine_v6::o6_getObjectNewDir() {
	push(getObjNewDir(pop()));
}

void ScummEngine_v6::o6_findInventory() {
	int idx = pop();
	int owner = pop();
	push(findInventory(owner, idx));
}

void ScummEngine_v6::o6_getInventoryCount() {
	push(getInventoryCount(pop()));
}

void ScummEngine_v6::o6_getVerbFromXY() {
	int y = pop();
	int x = pop();
	int over = findVerbAtPos(x, y);
	if (over)
		over = _verbs[over].verbid;
	push(over);
}

void ScummEngine_v6::o6_beginOverride() {
	beginOverride();
	_skipVideo = 0;
}

void ScummEngine_v6::o6_endOverride() {
	endOverride();
}

void ScummEngine_v6::o6_setObjectName() {
	int obj = pop();
	setObjectName(obj);
}

void ScummEngine_v6::o6_isSoundRunning() {
	int snd = pop();

	if (snd)
		snd = _sound->isSoundRunning(snd);

	push(snd);
}

void ScummEngine_v6::o6_setBoxFlags() {
	int table[65];
	int num, value;

	value = pop();
	num = getStackList(table, ARRAYSIZE(table));

	while (--num >= 0) {
		setBoxFlags(table[num], value);
	}
}

void ScummEngine_v6::o6_createBoxMatrix() {
	createBoxMatrix();

	if ((_game.id == GID_DIG) || (_game.id == GID_CMI))
		putActors();
}

void ScummEngine_v6::o6_resourceRoutines() {
	int resid;

	byte subOp = fetchScriptByte();

	switch (subOp) {
	case 100:		// SO_LOAD_SCRIPT
		resid = pop();
		if (_game.version >= 7)
			if (resid >= _numGlobalScripts)
				break;
		ensureResourceLoaded(rtScript, resid);
		break;
	case 101:		// SO_LOAD_SOUND
		resid = pop();
		ensureResourceLoaded(rtSound, resid);
		break;
	case 102:		// SO_LOAD_COSTUME
		resid = pop();
		ensureResourceLoaded(rtCostume, resid);
		break;
	case 103:		// SO_LOAD_ROOM
		resid = pop();
		ensureResourceLoaded(rtRoom, resid);
		break;
	case 104:		// SO_NUKE_SCRIPT
		resid = pop();
		if (_game.version >= 7)
			if (resid >= _numGlobalScripts)
				break;
		_res->setResourceCounter(rtScript, resid, 0x7F);
		break;
	case 105:		// SO_NUKE_SOUND
		resid = pop();
		_res->setResourceCounter(rtSound, resid, 0x7F);
		break;
	case 106:		// SO_NUKE_COSTUME
		resid = pop();
		_res->setResourceCounter(rtCostume, resid, 0x7F);
		break;
	case 107:		// SO_NUKE_ROOM
		resid = pop();
		_res->setResourceCounter(rtRoom, resid, 0x7F);
		break;
	case 108:		// SO_LOCK_SCRIPT
		resid = pop();
		if (resid >= _numGlobalScripts)
			break;
		_res->lock(rtScript, resid);
		break;
	case 109:		// SO_LOCK_SOUND
		resid = pop();
		_res->lock(rtSound, resid);
		break;
	case 110:		// SO_LOCK_COSTUME
		resid = pop();
		_res->lock(rtCostume, resid);
		break;
	case 111:		// SO_LOCK_ROOM
		resid = pop();
		if (resid > 0x7F)
			resid = _resourceMapper[resid & 0x7F];
		_res->lock(rtRoom, resid);
		break;
	case 112:		// SO_UNLOCK_SCRIPT
		resid = pop();
		if (resid >= _numGlobalScripts)
			break;
		_res->unlock(rtScript, resid);
		break;
	case 113:		// SO_UNLOCK_SOUND
		resid = pop();
		_res->unlock(rtSound, resid);
		break;
	case 114:		// SO_UNLOCK_COSTUME
		resid = pop();
		_res->unlock(rtCostume, resid);
		break;
	case 115:		// SO_UNLOCK_ROOM
		resid = pop();
		if (resid > 0x7F)
			resid = _resourceMapper[resid & 0x7F];
		_res->unlock(rtRoom, resid);
		break;
	case 116:		// SO_CLEAR_HEAP
		/* this is actually a scumm message */
		error("clear heap not working yet");
		break;
	case 117:		// SO_LOAD_CHARSET
		resid = pop();
		loadCharset(resid);
		break;
	case 118:		// SO_NUKE_CHARSET
		resid = pop();
		nukeCharset(resid);
		break;
	case 119:		// SO_LOAD_OBJECT
		{
			int room, obj = popRoomAndObj(&room);
			loadFlObject(obj, room);
			break;
		}
	default:
		error("o6_resourceRoutines: default case %d", subOp);
	}
}


void ScummEngine_v6::o6_roomOps() {
	int a, b, c, d, e;

	byte subOp = fetchScriptByte();

	switch (subOp) {
	case 172:		// SO_ROOM_SCROLL
		b = pop();
		a = pop();
		if (a < (_screenWidth / 2))
			a = (_screenWidth / 2);
		if (b < (_screenWidth / 2))
			b = (_screenWidth / 2);
		if (a > _roomWidth - (_screenWidth / 2))
			a = _roomWidth - (_screenWidth / 2);
		if (b > _roomWidth - (_screenWidth / 2))
			b = _roomWidth - (_screenWidth / 2);
		VAR(VAR_CAMERA_MIN_X) = a;
		VAR(VAR_CAMERA_MAX_X) = b;
		break;

	case 174:		// SO_ROOM_SCREEN
		b = pop();
		a = pop();
		initScreens(a, b);
		break;

	case 175:		// SO_ROOM_PALETTE
		d = pop();
		c = pop();
		b = pop();
		a = pop();
		setPalColor(d, a, b, c);
		break;

	case 176:		// SO_ROOM_SHAKE_ON
		setShake(1);
		break;

	case 177:		// SO_ROOM_SHAKE_OFF
		setShake(0);
		break;

	case 179:		// SO_ROOM_INTENSITY
		c = pop();
		b = pop();
		a = pop();
		darkenPalette(a, a, a, b, c);
		break;

	case 180:		// SO_ROOM_SAVEGAME
		_saveTemporaryState = true;
		_saveLoadSlot = pop();
		_saveLoadFlag = pop();
		if (_game.id == GID_TENTACLE)
			_saveSound = (_saveLoadSlot != 0);
		break;

	case 181:		// SO_ROOM_FADE
		a = pop();
		if (a) {
			_switchRoomEffect = (byte)(a & 0xFF);
			_switchRoomEffect2 = (byte)(a >> 8);
		} else {
			fadeIn(_newEffect);
		}
		break;

	case 182:		// SO_RGB_ROOM_INTENSITY
		e = pop();
		d = pop();
		c = pop();
		b = pop();
		a = pop();
		darkenPalette(a, b, c, d, e);
		break;

	case 183:		// SO_ROOM_SHADOW
		e = pop();
		d = pop();
		c = pop();
		b = pop();
		a = pop();
		setShadowPalette(a, b, c, d, e, 0, 256);
		break;

	case 184:		// SO_SAVE_STRING
		error("save string not implemented");
		break;

	case 185:		// SO_LOAD_STRING
		error("load string not implemented");
		break;

	case 186:		// SO_ROOM_TRANSFORM
		d = pop();
		c = pop();
		b = pop();
		a = pop();
		palManipulateInit(a, b, c, d);
		break;

	case 187:		// SO_CYCLE_SPEED
		b = pop();
		a = pop();
		assertRange(1, a, 16, "o6_roomOps: 187: color cycle");
		_colorCycle[a - 1].delay = (b != 0) ? 0x4000 / (b * 0x4C) : 0;
		break;

	case 213:		// SO_ROOM_NEW_PALETTE
		a = pop();

		// This opcode is used when turning off noir mode in Sam & Max,
		// but since our implementation of this feature doesn't change
		// the original palette there's no need to reload it. Doing it
		// this way, we avoid some graphics glitches that the original
		// interpreter had.

		if (_game.id == GID_SAMNMAX && vm.slot[_currentScript].number == 64)
			setDirtyColors(0, 255);
		else
			setCurrentPalette(a);
		break;
	default:
		error("o6_roomOps: default case %d", subOp);
	}
}

void ScummEngine_v6::o6_actorOps() {
	Actor *a;
	int i, j, k;
	int args[8];

	byte subOp = fetchScriptByte();
	if (subOp == 197) {
		_curActor = pop();
		return;
	}

	a = derefActorSafe(_curActor, "o6_actorOps");
	if (!a)
		return;

	switch (subOp) {
	case 76:		// SO_COSTUME
		a->setActorCostume(pop());
		break;
	case 77:		// SO_STEP_DIST
		j = pop();
		i = pop();
		a->setActorWalkSpeed(i, j);
		break;
	case 78:		// SO_SOUND
		k = getStackList(args, ARRAYSIZE(args));
		for (i = 0; i < k; i++)
			a->_sound[i] = args[i];
		break;
	case 79:		// SO_WALK_ANIMATION
		a->_walkFrame = pop();
		break;
	case 80:		// SO_TALK_ANIMATION
		a->_talkStopFrame = pop();
		a->_talkStartFrame = pop();
		break;
	case 81:		// SO_STAND_ANIMATION
		a->_standFrame = pop();
		break;
	case 82:		// SO_ANIMATION
		// dummy case in scumm6
		pop();
		pop();
		pop();
		break;
	case 83:		// SO_DEFAULT
		a->initActor(0);
		break;
	case 84:		// SO_ELEVATION
		a->setElevation(pop());
		break;
	case 85:		// SO_ANIMATION_DEFAULT
		a->_initFrame = 1;
		a->_walkFrame = 2;
		a->_standFrame = 3;
		a->_talkStartFrame = 4;
		a->_talkStopFrame = 5;
		break;
	case 86:		// SO_PALETTE
		j = pop();
		i = pop();
		assertRange(0, i, 255, "o6_actorOps: palette slot");
		a->setPalette(i, j);
		break;
	case 87:		// SO_TALK_COLOR
		a->_talkColor = pop();
		break;
	case 88:		// SO_ACTOR_NAME
		loadPtrToResource(rtActorName, a->_number, NULL);
		break;
	case 89:		// SO_INIT_ANIMATION
		a->_initFrame = pop();
		break;
	case 91:		// SO_ACTOR_WIDTH
		a->_width = pop();
		break;
	case 92:		// SO_SCALE
		i = pop();
		a->setScale(i, i);
		break;
	case 93:		// SO_NEVER_ZCLIP
		a->_forceClip = 0;
		break;
	case 225:		// SO_ALWAYS_ZCLIP
	case 94:		// SO_ALWAYS_ZCLIP
		a->_forceClip = pop();
		break;
	case 95:		// SO_IGNORE_BOXES
		a->_ignoreBoxes = 1;
		a->_forceClip = (_game.version >= 7) ? 100 : 0;
		if (a->isInCurrentRoom())
			a->putActor();
		break;
	case 96:		// SO_FOLLOW_BOXES
		a->_ignoreBoxes = 0;
		a->_forceClip = (_game.version >= 7) ? 100 : 0;
		if (a->isInCurrentRoom())
			a->putActor();
		break;
	case 97:		// SO_ANIMATION_SPEED
		a->setAnimSpeed(pop());
		break;
	case 98:		// SO_SHADOW
		a->_shadowMode = pop();
		break;
	case 99:		// SO_TEXT_OFFSET
		a->_talkPosY = pop();
		a->_talkPosX = pop();
		break;
	case 198:		// SO_ACTOR_VARIABLE
		i = pop();
		a->setAnimVar(pop(), i);
		break;
	case 215:		// SO_ACTOR_IGNORE_TURNS_ON
		a->_ignoreTurns = true;
		break;
	case 216:		// SO_ACTOR_IGNORE_TURNS_OFF
		a->_ignoreTurns = false;
		break;
	case 217:		// SO_ACTOR_NEW
		a->initActor(2);
		break;
	case 227:		// SO_ACTOR_DEPTH
		a->_layer = pop();
		break;
	case 228:		// SO_ACTOR_WALK_SCRIPT
		a->_walkScript = pop();
		break;
	case 229:		// SO_ACTOR_STOP
		a->stopActorMoving();
		a->startAnimActor(a->_standFrame);
		break;
	case 230:										/* set direction */
		a->_moving &= ~MF_TURN;
		a->setDirection(pop());
		break;
	case 231:										/* turn to direction */
		a->turnToDirection(pop());
		break;
	case 233:		// SO_ACTOR_WALK_PAUSE
		a->_moving |= MF_FROZEN;
		break;
	case 234:		// SO_ACTOR_WALK_RESUME
		a->_moving &= ~MF_FROZEN;
		break;
	case 235:		// SO_ACTOR_TALK_SCRIPT
		a->_talkScript = pop();
		break;
	default:
		error("o6_actorOps: default case %d", subOp);
	}
}

void ScummEngine_v6::o6_verbOps() {
	int slot, a, b;
	VerbSlot *vs;

	byte subOp = fetchScriptByte();
	if (subOp == 196) {
		_curVerb = pop();
		_curVerbSlot = getVerbSlot(_curVerb, 0);
		assertRange(0, _curVerbSlot, _numVerbs - 1, "new verb slot");
		return;
	}
	vs = &_verbs[_curVerbSlot];
	slot = _curVerbSlot;
	switch (subOp) {
	case 124:		// SO_VERB_IMAGE
		a = pop();
		if (_curVerbSlot) {
			setVerbObject(_roomResource, a, slot);
			vs->type = kImageVerbType;
			if (_game.heversion >= 61)
				vs->imgindex = a;
		}
		break;
	case 125:		// SO_VERB_NAME
		loadPtrToResource(rtVerb, slot, NULL);
		vs->type = kTextVerbType;
		vs->imgindex = 0;
		break;
	case 126:		// SO_VERB_COLOR
		vs->color = pop();
		break;
	case 127:		// SO_VERB_HICOLOR
		vs->hicolor = pop();
		break;
	case 128:		// SO_VERB_AT
		vs->curRect.top = pop();
		vs->curRect.left = pop();
		break;
	case 129:		// SO_VERB_ON
		vs->curmode = 1;
		break;
	case 130:		// SO_VERB_OFF
		vs->curmode = 0;
		break;
	case 131:		// SO_VERB_DELETE
		if (_game.heversion >= 60) {
			slot = getVerbSlot(pop(), 0);
		}
		killVerb(slot);
		break;
	case 132:		// SO_VERB_NEW
		slot = getVerbSlot(_curVerb, 0);
		if (slot == 0) {
			for (slot = 1; slot < _numVerbs; slot++) {
				if (_verbs[slot].verbid == 0)
					break;
			}
			if (slot == _numVerbs)
				error("Too many verbs");
			_curVerbSlot = slot;
		}
		vs = &_verbs[slot];
		vs->verbid = _curVerb;
		vs->color = 2;
		vs->hicolor = 0;
		vs->dimcolor = 8;
		vs->type = kTextVerbType;
		vs->charset_nr = _string[0]._default.charset;
		vs->curmode = 0;
		vs->saveid = 0;
		vs->key = 0;
		vs->center = 0;
		vs->imgindex = 0;
		break;
	case 133:		// SO_VERB_DIMCOLOR
		vs->dimcolor = pop();
		break;
	case 134:		// SO_VERB_DIM
		vs->curmode = 2;
		break;
	case 135:		// SO_VERB_KEY
		vs->key = pop();
		break;
	case 136:		// SO_VERB_CENTER
		vs->center = 1;
		break;
	case 137:		// SO_VERB_NAME_STR
		a = pop();
		if (a == 0) {
			loadPtrToResource(rtVerb, slot, (const byte *)"");
		} else {
			loadPtrToResource(rtVerb, slot, getStringAddress(a));
		}
		vs->type = kTextVerbType;
		vs->imgindex = 0;
		break;
	case 139:		// SO_VERB_IMAGE_IN_ROOM
		b = pop();
		a = pop();

		if (slot && a != vs->imgindex) {
			setVerbObject(b, a, slot);
			vs->type = kImageVerbType;
			vs->imgindex = a;
		}
		break;
	case 140:		// SO_VERB_BAKCOLOR
		vs->bkcolor = pop();
		break;
	case 255:
		drawVerb(slot, 0);
		verbMouseOver(0);
		break;
	default:
		error("o6_verbops: default case %d", subOp);
	}
}

void ScummEngine_v6::o6_getActorFromXY() {
	int y = pop();
	int x = pop();
	int r = getActorFromPos(x, y);
	push(r);
}

void ScummEngine_v6::o6_findObject() {
	int y = pop();
	int x = pop();
	int r = findObject(x, y);
	push(r);
}

void ScummEngine_v6::o6_pseudoRoom() {
	int list[100];
	int num, a, value;

	num = getStackList(list, ARRAYSIZE(list));
	value = pop();

	while (--num >= 0) {
		a = list[num];
		if (a > 0x7F)
			_resourceMapper[a & 0x7F] = value;
	}
}

void ScummEngine_v6::o6_getVerbEntrypoint() {
	int e = pop();
	int v = pop();
	push(getVerbEntrypoint(v, e));
}

void ScummEngine_v6::o6_arrayOps() {
	byte subOp = fetchScriptByte();
	int array = fetchScriptWord();
	int b, c, d, len;
	byte *data;
	int list[128];

	switch (subOp) {
	case 205:		// SO_ASSIGN_STRING
		b = pop();
		len = resStrLen(_scriptPointer);
		data = defineArray(array, kStringArray, 0, len + 1);
		copyScriptString(data + b);
		break;
	case 208:		// SO_ASSIGN_INT_LIST
		b = pop();
		c = pop();
		d = readVar(array);
		if (d == 0) {
			defineArray(array, kIntArray, 0, b + c);
		}
		while (c--) {
			writeArray(array, 0, b + c, pop());
		}
		break;
	case 212:		// SO_ASSIGN_2DIM_LIST
		b = pop();
		len = getStackList(list, ARRAYSIZE(list));
		d = readVar(array);
		if (d == 0)
			error("Must DIM a two dimensional array before assigning");
		c = pop();
		while (--len >= 0) {
			writeArray(array, c, b + len, list[len]);
		}
		break;
	default:
		error("o6_arrayOps: default case %d (array %d)", subOp, array);
	}
}

void ScummEngine_v6::o6_saveRestoreVerbs() {
	int a, b, c;
	int slot, slot2;

	c = pop();
	b = pop();
	a = pop();

	byte subOp = fetchScriptByte();
	if (_game.version == 8) {
		subOp = (subOp - 141) + 0xB4;
	}

	switch (subOp) {
	case 141:		// SO_SAVE_VERBS
		while (a <= b) {
			slot = getVerbSlot(a, 0);
			if (slot && _verbs[slot].saveid == 0) {
				_verbs[slot].saveid = c;
				drawVerb(slot, 0);
				verbMouseOver(0);
			}
			a++;
		}
		break;
	case 142:		// SO_RESTORE_VERBS
		while (a <= b) {
			slot = getVerbSlot(a, c);
			if (slot) {
				slot2 = getVerbSlot(a, 0);
				if (slot2)
					killVerb(slot2);
				slot = getVerbSlot(a, c);
				_verbs[slot].saveid = 0;
				drawVerb(slot, 0);
				verbMouseOver(0);
			}
			a++;
		}
		break;
	case 143:		// SO_DELETE_VERBS
		while (a <= b) {
			slot = getVerbSlot(a, c);
			if (slot)
				killVerb(slot);
			a++;
		}
		break;
	default:
		error("o6_saveRestoreVerbs: default case");
	}
}

void ScummEngine_v6::o6_drawBox() {
	int x, y, x2, y2, color;
	color = pop();
	y2 = pop();
	x2 = pop();
	y = pop();
	x = pop();
	drawBox(x, y, x2, y2, color);
}

void ScummEngine_v6::o6_wait() {
	int actnum;
	int offs = -2;
	Actor *a;

	byte subOp = fetchScriptByte();

	switch (subOp) {
	case 168:		// SO_WAIT_FOR_ACTOR Wait for actor
		offs = fetchScriptWordSigned();
		actnum = pop();
		a = derefActor(actnum, "o6_wait:168");
		if (_game.version >= 7) {
			if (a->isInCurrentRoom() && a->_moving)
				break;
		} else {
			if (a->_moving)
				break;
		}
		return;
	case 169:		// SO_WAIT_FOR_MESSAGE Wait for message
		if (VAR(VAR_HAVE_MSG))
			break;
		return;
	case 170:		// SO_WAIT_FOR_CAMERA Wait for camera
		if (_game.version >= 7) {
			if (camera._dest != camera._cur)
				break;
		} else {
			if (camera._cur.x / 8 != camera._dest.x / 8)
				break;
		}

		return;
	case 171:		// SO_WAIT_FOR_SENTENCE
		if (_sentenceNum) {
			if (_sentence[_sentenceNum - 1].freezeCount && !isScriptInUse(VAR(VAR_SENTENCE_SCRIPT)))
				return;
			break;
		}
		if (!isScriptInUse(VAR(VAR_SENTENCE_SCRIPT)))
			return;
		break;
	case 226:		// SO_WAIT_FOR_ANIMATION
		offs = fetchScriptWordSigned();
		actnum = pop();
		a = derefActor(actnum, "o6_wait:226");
		if (a->isInCurrentRoom() && a->_needRedraw)
			break;
		return;
	case 232:		// SO_WAIT_FOR_TURN
		// WORKAROUND for bug #744441: An angle will often be received as the
		// actor number due to script bugs in The Dig. In all cases where this
		// occurs, _curActor is set just before it, so we can use it instead.
		//
		// For now, if the value passed in is divisible by 45, assume it is an
		// angle, and use _curActor as the actor to wait for.
		offs = fetchScriptWordSigned();
		actnum = pop();
		if (actnum % 45 == 0) {
			actnum = _curActor;
		}
		a = derefActor(actnum, "o6_wait:232b");
		if (a->isInCurrentRoom() && a->_moving & MF_TURN)
			break;
		return;
	default:
		error("o6_wait: default case 0x%x", subOp);
	}

	_scriptPointer += offs;
	o6_breakHere();
}

void ScummEngine_v6::o6_soundKludge() {
	int list[16];
	int num = getStackList(list, ARRAYSIZE(list));

	_sound->soundKludge(list, num);

	// WORKAROUND for bug #1398195: The room-11-2016 script contains a
	// slight bug causing it to busy-wait for a sound to finish. Even under
	// the best of circumstances, this will cause the game to hang briefly.
	// On platforms where threading is cooperative, it will cause the game
	// to hang indefinitely. We identify the buggy part of the script by
	// looking for a soundKludge() opcode immediately followed by a jump.

	if (_game.id == GID_CMI && _roomResource == 11 && vm.slot[_currentScript].number == 2016 && *_scriptPointer == 0x66) {
		debug(3, "Working around script bug in room-11-2016");
		o6_breakHere();
	}
}

void ScummEngine_v6::o6_isAnyOf() {
	int list[100];
	int num;
	int32 val;

	num = getStackList(list, ARRAYSIZE(list));
	val = pop();

	while (--num >= 0) {
		if (list[num] == val) {
			push(1);
			return;
		}
	}

	push(0);
}

void ScummEngine_v6::o6_systemOps() {
	byte subOp = fetchScriptByte();

	switch (subOp) {
	case 158:		// SO_RESTART
		restart();
		break;
	case 159:		// SO_PAUSE
		pauseGame();
		break;
	case 160:		// SO_QUIT
		quitGame();
		break;
	default:
		error("o6_systemOps invalid case %d", subOp);
	}
}

void ScummEngine_v6::o6_delay() {
	uint32 delay = (uint16)pop();
	vm.slot[_currentScript].delay = delay;
	vm.slot[_currentScript].status = ssPaused;
	o6_breakHere();
}

void ScummEngine_v6::o6_delaySeconds() {
	uint32 delay = (uint32)pop();
	delay = delay * 60;
	vm.slot[_currentScript].delay = delay;
	vm.slot[_currentScript].status = ssPaused;
	o6_breakHere();
}

void ScummEngine_v6::o6_delayMinutes() {
	uint32 delay = (uint16)pop() * 3600;
	vm.slot[_currentScript].delay = delay;
	vm.slot[_currentScript].status = ssPaused;
	o6_breakHere();
}

void ScummEngine_v6::o6_stopSentence() {
	_sentenceNum = 0;
	stopScript(VAR(VAR_SENTENCE_SCRIPT));
	clearClickedStatus();
}

void ScummEngine_v6::o6_printLine() {
	_actorToPrintStrFor = 0xFF;
	decodeParseString(0, 0);
}

void ScummEngine_v6::o6_printText() {
	decodeParseString(1, 0);
}

void ScummEngine_v6::o6_printDebug() {
	decodeParseString(2, 0);
}

void ScummEngine_v6::o6_printSystem() {
	decodeParseString(3, 0);
}

void ScummEngine_v6::o6_printActor() {
	decodeParseString(0, 1);
}

void ScummEngine_v6::o6_printEgo() {
	push(VAR(VAR_EGO));
	decodeParseString(0, 1);
}

void ScummEngine_v6::o6_talkActor() {
	int offset = _scriptPointer - _scriptOrgPointer;

	// WORKAROUND for bug #896489: see below for detailed description
	if (_forcedWaitForMessage) {
		if (VAR(VAR_HAVE_MSG)) {
			_scriptPointer--;
			o6_breakHere();
			return;
		}

		_forcedWaitForMessage = false;
		_scriptPointer += resStrLen(_scriptPointer) + 1;

		return;
	}

	_actorToPrintStrFor = pop();

	// WORKAROUND for bug #2016521: "DOTT: Bernard impersonating LaVerne"
	// Original script did not check for VAR_EGO == 2 before executing
	// a talkActor opcode.
	if (_game.id == GID_TENTACLE && vm.slot[_currentScript].number == 307
			&& VAR(VAR_EGO) != 2 && _actorToPrintStrFor == 2) {
		_scriptPointer += resStrLen(_scriptPointer) + 1;
		return;
	}

	_string[0].loadDefault();
	actorTalk(_scriptPointer);

	// WORKAROUND for bug #896489: "DIG: Missing subtitles when talking to Brink"
	// Original script does not have wait.waitForMessage() after several messages:
	//
	// [011A] (5D)   if (getActorCostume(VAR_EGO) == 1) {
	// [0126] (BA)     talkActor("/STOP.008/Low out.",3)
	// [013D] (A9)     wait.waitForMessage()
	// [013F] (5D)   } else if (var227 == 0) {
	// [014C] (BA)     talkActor("/STOP.009/Never mind.",3)
	// [0166] (73)   } else {
	//
	// Here we simulate that opcode.
	if (_game.id == GID_DIG && vm.slot[_currentScript].number == 88) {
		if (offset == 0x158 || offset == 0x214 || offset == 0x231 || offset == 0x278) {
			_forcedWaitForMessage = true;
			_scriptPointer--;

			return;
		}
	}
	_scriptPointer += resStrLen(_scriptPointer) + 1;
}

void ScummEngine_v6::o6_talkEgo() {
	push(VAR(VAR_EGO));
	o6_talkActor();
}

void ScummEngine_v6::o6_dimArray() {
	int data;

	byte subOp = fetchScriptByte();

	switch (subOp) {
	case 199:		// SO_INT_ARRAY
		data = kIntArray;
		break;
	case 200:		// SO_BIT_ARRAY
		data = kBitArray;
		break;
	case 201:		// SO_NIBBLE_ARRAY
		data = kNibbleArray;
		break;
	case 202:		// SO_BYTE_ARRAY
		data = kByteArray;
		break;
	case 203:		// SO_STRING_ARRAY
		data = kStringArray;
		break;
	case 204:		// SO_UNDIM_ARRAY
		nukeArray(fetchScriptWord());
		return;
	default:
		error("o6_dimArray: default case %d", subOp);
	}

	defineArray(fetchScriptWord(), data, 0, pop());
}

void ScummEngine_v6::o6_dummy() {
}

void ScummEngine_v6::o6_dim2dimArray() {
	int a, b, data;

	byte subOp = fetchScriptByte();

	switch (subOp) {
	case 199:		// SO_INT_ARRAY
		data = kIntArray;
		break;
	case 200:		// SO_BIT_ARRAY
		data = kBitArray;
		break;
	case 201:		// SO_NIBBLE_ARRAY
		data = kNibbleArray;
		break;
	case 202:		// SO_BYTE_ARRAY
		data = kByteArray;
		break;
	case 203:		// SO_STRING_ARRAY
		data = kStringArray;
		break;
	default:
		error("o6_dim2dimArray: default case %d", subOp);
	}

	b = pop();
	a = pop();
	defineArray(fetchScriptWord(), data, a, b);
}

void ScummEngine_v6::o6_abs() {
	int a = pop();
	push(ABS(a));
}

void ScummEngine_v6::o6_distObjectObject() {
	int a, b;
	b = pop();
	a = pop();
	push(getDistanceBetween(true, a, 0, true, b, 0));
}

void ScummEngine_v6::o6_distObjectPt() {
	int a, b, c;
	c = pop();
	b = pop();
	a = pop();
	push(getDistanceBetween(true, a, 0, false, b, c));
}

void ScummEngine_v6::o6_distPtPt() {
	int a, b, c, d;
	d = pop();
	c = pop();
	b = pop();
	a = pop();
	push(getDistanceBetween(false, a, b, false, c, d));
}

void ScummEngine_v6::o6_drawBlastObject() {
	int args[16];
	int a, b, c, d, e;

	getStackList(args, ARRAYSIZE(args));
	e = pop();
	d = pop();
	c = pop();
	b = pop();
	a = pop();
	enqueueObject(a, b, c, d, e, 0xFF, 0xFF, 1, 0);
}

// Set BOMP processing window
void ScummEngine_v6::o6_setBlastObjectWindow() {
	pop();
	pop();
	pop();
	pop();

	// None of the scripts of The Dig and Full Throttle use this opcode.
	// Sam & Max only uses it at the beginning of the highway subgame. In
	// the original interpreter pop'ed arguments are just ignored and the
	// clipping blastObject window is defined with (0, 0, 320, 200)...
	// which matches the screen dimensions and thus, doesn't require
	// another clipping operation.
	// So, we just handle this as no-op opcode.
}

#ifdef ENABLE_SCUMM_7_8
void ScummEngine_v7::o6_kernelSetFunctions() {
	int args[30];
	int num;
	Actor *a;

	num = getStackList(args, ARRAYSIZE(args));

	switch (args[0]) {
	case 4:
		grabCursor(args[1], args[2], args[3], args[4]);
		break;
	case 6: {
			// SMUSH movie playback
			if (args[1] == 0 && !_skipVideo) {
				const char *videoname = (const char *)getStringAddressVar(VAR_VIDEONAME);
				assert(videoname);

				// Correct incorrect smush filename in Macintosh FT demo
				if ((_game.id == GID_FT) && (_game.features & GF_DEMO) && (_game.platform == Common::kPlatformMacintosh) &&
					(!strcmp(videoname, "jumpgorge.san")))
					_splayer->play("jumpgorg.san", _smushFrameRate);
				// WORKAROUND: A faster frame rate is required, to keep audio/video in sync in this video
				else if (_game.id == GID_DIG && !strcmp(videoname, "sq3.san"))
					_splayer->play(videoname, 14);
				else
					_splayer->play(videoname, _smushFrameRate);

				if (_game.id == GID_DIG) {
					_disableFadeInEffect = true;
				}
			} else if (_game.id == GID_FT && !_skipVideo) {
				const int insaneVarNum = ((_game.features & GF_DEMO) && (_game.platform == Common::kPlatformDOS))
					? 232 : 233;

				_insane->setSmushParams(_smushFrameRate);
				_insane->runScene(insaneVarNum);
			}
		}
		break;
	case 12:
		setCursorFromImg(args[1], (uint) - 1, args[2]);
		break;
	case 13:
		derefActor(args[1], "o6_kernelSetFunctions:13")->remapActorPalette(args[2], args[3], args[4], -1);
		break;
	case 14:
		derefActor(args[1], "o6_kernelSetFunctions:14")->remapActorPalette(args[2], args[3], args[4], args[5]);
		break;
	case 15:
		_smushFrameRate = args[1];
		break;
	case 16:
	case 17:
		enqueueText(getStringAddressVar(VAR_STRING2DRAW), args[3], args[4], args[2], args[1], (args[0] == 16));
		break;
	case 20:
		_imuseDigital->setRadioChatterSFX(args[1]);
		break;
	case 107:
		a = derefActor(args[1], "o6_kernelSetFunctions: 107");
		a->setScale((unsigned char)args[2], -1);
		break;
	case 108:
		setShadowPalette(args[1], args[2], args[3], args[4], args[5], args[6]);
		break;
	case 109:
		setShadowPalette(0, args[1], args[2], args[3], args[4], args[5]);
		break;
	case 114:
		error("o6_kernelSetFunctions: stub114()");
		break;
	case 117:
		freezeScripts(2);
		break;
	case 118:
		enqueueObject(args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], 3);
		break;
	case 119:
		enqueueObject(args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], 0);
		break;
	case 124:
		_saveSound = args[1];
		break;
	case 215:
		ConfMan.setBool("subtitles", args[1] != 0);
		break;
	default:
		error("o6_kernelSetFunctions: default case %d (param count %d)", args[0], num);
		break;
	}
}
#endif

void ScummEngine_v6::o6_kernelSetFunctions() {
	int args[30];
	int num;
	Actor *a;

	num = getStackList(args, ARRAYSIZE(args));

	switch (args[0]) {
	case 3:
		// Dummy case
		break;
	case 4:
		grabCursor(args[1], args[2], args[3], args[4]);
		break;
	case 5:
		fadeOut(args[1]);
		break;
	case 6:
		_fullRedraw = true;
		redrawBGAreas();
		setActorRedrawFlags();
		processActors();
		fadeIn(args[1]);
		break;
	case 8:
		if (startManiac()) {
			// This is so that the surprised exclamation happens
			// after we return to the game again, not before.
			o6_breakHere();
		}
		break;
	case 9:
		killAllScriptsExceptCurrent();
		break;
	case 104:									/* samnmax */
		nukeFlObjects(args[2], args[3]);
		break;
	case 107:									/* set actor scale */
		a = derefActor(args[1], "o6_kernelSetFunctions: 107");
		a->setScale((unsigned char)args[2], -1);
		break;
	case 108:									/* create proc_special_palette */
	case 109:
		// Case 108 and 109 share the same function
		if (num != 6)
			error("o6_kernelSetFunctions sub op %d: expected 6 params but got %d", args[0], num);
		setShadowPalette(args[3], args[4], args[5], args[1], args[2], 0, 256);
		break;
	case 110:
		clearCharsetMask();
		break;
	case 111:
		a = derefActor(args[1], "o6_kernelSetFunctions: 111");
		a->_shadowMode = args[2] + args[3];
		break;
	case 112:									/* palette shift? */
		setShadowPalette(args[3], args[4], args[5], args[1], args[2], args[6], args[7]);
		break;
	case 114:
		// Sam & Max film noir mode
		if (_game.id == GID_SAMNMAX) {
			// At this point ScummVM will already have set
			// variable 0x8000 to indicate that the game is
			// in film noir mode. All we have to do here is
			// to mark the palette as "dirty", because
			// updatePalette() will desaturate the colors
			// as they are uploaded to the backend.
			//
			// This actually works better than the original
			// interpreter, where actors would sometimes
			// still be drawn in color.
			setDirtyColors(0, 255);
		} else
			error("stub o6_kernelSetFunctions_114()");
		break;
	case 117:
		// Sam & Max uses this opcode in script-43, right
		// before a screensaver is selected.
		//
		// Sam & Max uses variable 132 to specify the number of
		// minutes of inactivity (no mouse movements) before
		// starting the screensaver, so setting it to 0 will
		// help in debugging.
		freezeScripts(0x80);
		break;
	case 119:
		enqueueObject(args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], 0);
		break;
	case 120:
		swapPalColors(args[1], args[2]);
		break;
	case 122:
		VAR(VAR_SOUNDRESULT) =
			(short)_imuse->doCommand (num - 1, &args[1]);
		break;
	case 123:
		copyPalColor(args[2], args[1]);
		break;
	case 124:
		_saveSound = args[1];
		break;
	default:
		error("o6_kernelSetFunctions: default case %d (param count %d)", args[0], num);
		break;
	}
}

void ScummEngine_v6::o6_kernelGetFunctions() {
	int args[30];
	int i;
	int slot;
	Actor *a;
	VirtScreen *vs = &_virtscr[kMainVirtScreen];

	getStackList(args, ARRAYSIZE(args));

	switch (args[0]) {
	case 113:
		// WORKAROUND for bug #899249: The scripts used for screen savers
		// in Sam & Max use hard coded values for the maximum height and width.
		// This causes problems in rooms (ie. Credits) where their values are
		// lower, so we set result to zero if out of bounds.
		if (args[1] >= 0 && args[1] <= vs->w && args[2] >= 0 && args[2] <= vs->h) {
			byte pixel = *vs->getPixels(args[1], args[2]);
			push(pixel);
		} else {
			push(0);
		}
		break;
	case 115:
		push(getSpecialBox(args[1], args[2]));
		break;
	case 116:
		push(checkXYInBoxBounds(args[3], args[1], args[2]));
		break;
	case 206:
		push(remapPaletteColor(args[1], args[2], args[3], -1));
		break;
	case 207:
		i = getObjectIndex(args[1]);
		assert(i);
		push(_objs[i].x_pos);
		break;
	case 208:
		i = getObjectIndex(args[1]);
		assert(i);
		push(_objs[i].y_pos);
		break;
	case 209:
		i = getObjectIndex(args[1]);
		assert(i);
		push(_objs[i].width);
		break;
	case 210:
		i = getObjectIndex(args[1]);
		assert(i);
		push(_objs[i].height);
		break;
	case 211:
		/*
		   13 = thrust
		   336 = thrust
		   328 = thrust
		   27 = abort
		   97 = left
		   331 = left
		   115 = right
		   333 = right
		 */

		push(getKeyState(args[1]));
		break;
	case 212:
		a = derefActor(args[1], "o6_kernelGetFunctions:212");
		// This is used by walk scripts
		push(a->_frame);
		break;
	case 213:
		slot = getVerbSlot(args[1], 0);
		push(_verbs[slot].curRect.left);
		break;
	case 214:
		slot = getVerbSlot(args[1], 0);
		push(_verbs[slot].curRect.top);
		break;
	case 215:
		if ((_extraBoxFlags[args[1]] & 0x00FF) == 0x00C0) {
			push(_extraBoxFlags[args[1]]);
		} else {
			push(getBoxFlags(args[1]));
		}
		break;
	default:
		error("o6_kernelGetFunctions: default case %d", args[0]);
	}
}

int ScummEngine::getKeyState(int key) {
	switch (key) {
	case 0x147: // Home
		// FIXME: There seems to be a mistake in the code here ("insert" vs. "home")
		return (_keyDownMap[Common::KEYCODE_KP7] ||
		        _keyDownMap[Common::KEYCODE_INSERT]) ? 1 : 0;
	case 0x148: // Up
		return (_keyDownMap[Common::KEYCODE_KP8] ||
		        _keyDownMap[Common::KEYCODE_UP] ||
				_keyDownMap[Common::KEYCODE_8]) ? 1 : 0;
	case 0x149: // PgUp
		return (_keyDownMap[Common::KEYCODE_KP9] ||
		        _keyDownMap[Common::KEYCODE_PAGEUP]) ? 1 : 0;
	case 0x14B: // Left
		return (_keyDownMap[Common::KEYCODE_KP4] ||
		        _keyDownMap[Common::KEYCODE_LEFT] ||
				_keyDownMap[Common::KEYCODE_4]) ? 1 : 0;
	case 0x14D: // Right
		return (_keyDownMap[Common::KEYCODE_KP6] ||
		        _keyDownMap[Common::KEYCODE_RIGHT] ||
				_keyDownMap[Common::KEYCODE_6]) ? 1 : 0;
	case 0x14F: // End
		return (_keyDownMap[Common::KEYCODE_KP1] ||
		        _keyDownMap[Common::KEYCODE_END]) ? 1 : 0;
	case 0x150: // Down
		return (_keyDownMap[Common::KEYCODE_KP2] ||
		        _keyDownMap[Common::KEYCODE_DOWN] ||
				_keyDownMap[Common::KEYCODE_2]) ? 1 : 0;
	case 0x151: // PgDn
		return (_keyDownMap[Common::KEYCODE_KP3] ||
		        _keyDownMap[Common::KEYCODE_PAGEDOWN]) ? 1 : 0;
	default:
		return (_keyDownMap[key]) ? 1 : 0;
	}
}

void ScummEngine_v6::o6_delayFrames() {
	ScriptSlot *ss = &vm.slot[_currentScript];
	if (ss->delayFrameCount == 0) {
		ss->delayFrameCount = pop();
	} else {
		ss->delayFrameCount--;
	}
	if (ss->delayFrameCount) {
		_scriptPointer--;
		o6_breakHere();
	}
}

void ScummEngine_v6::o6_pickOneOf() {
	int args[100];
	int i, num;

	num = getStackList(args, ARRAYSIZE(args));
	i = pop();
	if (i < 0 || i > num)
		error("o6_pickOneOf: %d out of range (0, %d)", i, num - 1);
	push(args[i]);
}

void ScummEngine_v6::o6_pickOneOfDefault() {
	int args[100];
	int i, num, def;

	def = pop();
	num = getStackList(args, ARRAYSIZE(args));
	i = pop();
	if (i < 0 || i >= num)
		i = def;
	else
		i = args[i];
	push(i);
}

void ScummEngine_v6::o6_stampObject() {
	int object, x, y, state;

	state = pop();
	y = pop();
	x = pop();
	object = pop();
	if (_game.version >= 7 && object < 30) {
		if (state == 0)
			state = 255;

		Actor *a = derefActor(object, "o6_stampObject");
		a->_scalex = state;
		a->_scaley = state;
		a->putActor(x, y, _currentRoom);
		a->_drawToBackBuf = true;
		a->drawActorCostume();
		a->_drawToBackBuf = false;
		a->drawActorCostume();
		return;
	}

	if (state == 0)
		state = 1;

	int objnum = getObjectIndex(object);
	if (objnum == -1)
		return;

	if (x != -1) {
		_objs[objnum].x_pos = x * 8;
		_objs[objnum].y_pos = y * 8;
	}

	putState(object, state);
	drawObject(objnum, 0);
}

void ScummEngine_v6::o6_stopTalking() {
	stopTalk();
}

void ScummEngine_v6::o6_findAllObjects() {
	int room = pop();
	int i = 1;

	if (room != _currentRoom)
		error("o6_findAllObjects: current room is not %d", room);
	writeVar(0, 0);
	defineArray(0, kIntArray, 0, _numLocalObjects + 1);
	writeArray(0, 0, 0, _numLocalObjects);

	while (i < _numLocalObjects) {
		writeArray(0, 0, i, _objs[i].obj_nr);
		i++;
	}

	push(readVar(0));
}

void ScummEngine_v6::shuffleArray(int num, int minIdx, int maxIdx) {
	int range = maxIdx - minIdx;
	int count = range * 2;

	// Shuffle the array 'num'
	while (count--) {
		// Determine two random elements...
		int rand1 = _rnd.getRandomNumber(range) + minIdx;
		int rand2 = _rnd.getRandomNumber(range) + minIdx;

		// ...and swap them
		int val1 = readArray(num, 0, rand1);
		int val2 = readArray(num, 0, rand2);
		writeArray(num, 0, rand1, val2);
		writeArray(num, 0, rand2, val1);
	}
}

void ScummEngine_v6::o6_shuffle() {
	int b = pop();
	int a = pop();
	shuffleArray(fetchScriptWord(), a, b);
}

void ScummEngine_v6::o6_pickVarRandom() {
	int num;
	int args[100];
	int dim1;

	num = getStackList(args, ARRAYSIZE(args));
	int value = fetchScriptWord();

	if (readVar(value) == 0) {
		defineArray(value, kIntArray, 0, num);
		if (num > 0) {
			int16 counter = 0;
			do {
				writeArray(value, 0, counter + 1, args[counter]);
			} while (++counter < num);
		}

		shuffleArray(value, 1, num);
		writeArray(value, 0, 0, 2);
		push(readArray(value, 0, 1));
		return;
	}

	num = readArray(value, 0, 0);

	ArrayHeader *ah = getArray(value);
	dim1 = FROM_LE_16(ah->dim1) - 1;

	if (dim1 < num) {
		int16 var_2 = readArray(value, 0, num - 1);
		shuffleArray(value, 1, dim1);
		if (readArray(value, 0, 1) == var_2) {
			num = 2;
		} else {
			num = 1;
		}
	}

	writeArray(value, 0, 0, num + 1);
	push(readArray(value, 0, num));
}

void ScummEngine_v6::o6_getDateTime() {
	TimeDate t;
	_system->getTimeAndDate(t);

	VAR(VAR_TIMEDATE_YEAR) = t.tm_year;
	VAR(VAR_TIMEDATE_MONTH) = t.tm_mon;
	VAR(VAR_TIMEDATE_DAY) = t.tm_mday;
	VAR(VAR_TIMEDATE_HOUR) = t.tm_hour;
	VAR(VAR_TIMEDATE_MINUTE) = t.tm_min;

	if (_game.version == 8)
		VAR(VAR_TIMEDATE_SECOND) = t.tm_sec;
}

void ScummEngine_v6::o6_getPixel() {
	int x, y;

	if (_game.heversion >= 61 && _game.heversion <= 62) {
		x = pop();
		y = pop();
	} else {
		y = pop();
		x = pop();
	}

	VirtScreen *vs = findVirtScreen(y);

	if (vs == NULL || x > _screenWidth - 1 || x < 0) {
		push(-1);
		return;
	}

	byte pixel = *vs->getPixels(x, y - vs->topline);
	push(pixel);
}

void ScummEngine_v6::o6_setBoxSet() {
	int arg = pop() - 1;

	const byte *room = getResourceAddress(rtRoom, _roomResource);
	const byte *boxd = NULL, *boxm = NULL;
	int32 dboxSize, mboxSize;
	int i;

	ResourceIterator boxds(room, false);
	for (i = 0; i < arg; i++)
		boxd = boxds.findNext(MKTAG('B','O','X','D'));

	if (!boxd)
		error("ScummEngine_v6::o6_setBoxSet: Can't find dboxes for set %d", arg);

	dboxSize = READ_BE_UINT32(boxd + 4) - 8;
	byte *matrix = _res->createResource(rtMatrix, 2, dboxSize);

	assert(matrix);
	memcpy(matrix, boxd + 8, dboxSize);

	ResourceIterator boxms(room, false);
	for (i = 0; i < arg; i++)
		boxm = boxms.findNext(MKTAG('B','O','X','M'));

	if (!boxm)
		error("ScummEngine_v6::o6_setBoxSet: Can't find mboxes for set %d", arg);

	mboxSize = READ_BE_UINT32(boxm + 4) - 8;
	matrix = _res->createResource(rtMatrix, 1, mboxSize);

	assert(matrix);
	memcpy(matrix, boxm + 8, mboxSize);

	if (_game.version == 7)
		putActors();
}

void ScummEngine_v6::decodeParseString(int m, int n) {
	byte b = fetchScriptByte();

	switch (b) {
	case 65:		// SO_AT
		_string[m].ypos = pop();
		_string[m].xpos = pop();
		_string[m].overhead = false;
		break;
	case 66:		// SO_COLOR
		_string[m].color = pop();
		break;
	case 67:		// SO_CLIPPED
		_string[m].right = pop();
		break;
	case 69:		// SO_CENTER
		_string[m].center = true;
		_string[m].overhead = false;
		break;
	case 71:		// SO_LEFT
		_string[m].center = false;
		_string[m].overhead = false;
		break;
	case 72:		// SO_OVERHEAD
		_string[m].overhead = true;
		_string[m].no_talk_anim = false;
		break;
	case 74:		// SO_MUMBLE
		_string[m].no_talk_anim = true;
		break;
	case 75:		// SO_TEXTSTRING
		printString(m, _scriptPointer);
		_scriptPointer += resStrLen(_scriptPointer) + 1;
		break;
	case 0xFE:
		_string[m].loadDefault();
		if (n)
			_actorToPrintStrFor = pop();
		break;
	case 0xFF:
		_string[m].saveDefault();
		break;
	default:
		error("decodeParseString: default case 0x%x", b);
	}
}

} // End of namespace Scumm
