/* 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/akos.h"
#include "scumm/charset.h"
#include "scumm/file.h"
#include "scumm/imuse_digi/dimuse.h"
#include "scumm/object.h"
#include "scumm/resource.h"
#include "scumm/scumm_v8.h"
#include "scumm/sound.h"
#include "scumm/util.h"
#include "scumm/verbs.h"
#include "scumm/smush/smush_player.h"

#include "audio/mixer.h"

namespace Scumm {

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

void ScummEngine_v8::setupOpcodes() {
	/* 00 */
	OPCODE(0x01, o6_pushWord);
	OPCODE(0x02, o6_pushWordVar);
	OPCODE(0x03, o6_wordArrayRead);
	/* 04 */
	OPCODE(0x04, o6_wordArrayIndexedRead);
	OPCODE(0x05, o6_dup);
	OPCODE(0x06, o6_pop);
	OPCODE(0x07, o6_not);
	/* 08 */
	OPCODE(0x08, o6_eq);
	OPCODE(0x09, o6_neq);
	OPCODE(0x0a, o6_gt);
	OPCODE(0x0b, o6_lt);
	/* 0C */
	OPCODE(0x0c, o6_le);
	OPCODE(0x0d, o6_ge);
	OPCODE(0x0e, o6_add);
	OPCODE(0x0f, o6_sub);
	/* 10 */
	OPCODE(0x10, o6_mul);
	OPCODE(0x11, o6_div);
	OPCODE(0x12, o6_land);
	OPCODE(0x13, o6_lor);
	/* 14 */
	OPCODE(0x14, o6_band);
	OPCODE(0x15, o6_bor);
	OPCODE(0x16, o8_mod);
	/* 18 */
	/* 1C */
	/* 20 */
	/* 24 */
	/* 28 */
	/* 2C */
	/* 30 */
	/* 34 */
	/* 38 */
	/* 3C */
	/* 40 */
	/* 44 */
	/* 48 */
	/* 4C */
	/* 50 */
	/* 54 */
	/* 58 */
	/* 5C */
	/* 60 */
	/* 64 */
	OPCODE(0x64, o6_if);
	OPCODE(0x65, o6_ifNot);
	OPCODE(0x66, o6_jump);
	OPCODE(0x67, o6_breakHere);
	/* 68 */
	OPCODE(0x68, o6_delayFrames);
	OPCODE(0x69, o8_wait);
	OPCODE(0x6a, o6_delay);
	OPCODE(0x6b, o6_delaySeconds);
	/* 6C */
	OPCODE(0x6c, o6_delayMinutes);
	OPCODE(0x6d, o6_writeWordVar);
	OPCODE(0x6e, o6_wordVarInc);
	OPCODE(0x6f, o6_wordVarDec);
	/* 70 */
	OPCODE(0x70, o8_dimArray);
	OPCODE(0x71, o6_wordArrayWrite);
	OPCODE(0x72, o6_wordArrayInc);
	OPCODE(0x73, o6_wordArrayDec);
	/* 74 */
	OPCODE(0x74, o8_dim2dimArray);
	OPCODE(0x75, o6_wordArrayIndexedWrite);
	OPCODE(0x76, o8_arrayOps);
	/* 78 */
	OPCODE(0x79, o6_startScript);
	OPCODE(0x7a, o6_startScriptQuick);
	OPCODE(0x7b, o6_stopObjectCode);
	/* 7C */
	OPCODE(0x7c, o6_stopScript);
	OPCODE(0x7d, o6_jumpToScript);
	OPCODE(0x7e, o6_dummy);				// O_RETURN boils down to a NOP
	OPCODE(0x7f, o6_startObject);
	/* 80 */
	OPCODE(0x80, o6_stopObjectScript);
	OPCODE(0x81, o6_cutscene);
	OPCODE(0x82, o6_endCutscene);
	OPCODE(0x83, o6_freezeUnfreeze);
	/* 84 */
	OPCODE(0x84, o6_beginOverride);
	OPCODE(0x85, o6_endOverride);
	OPCODE(0x86, o6_stopSentence);
	/* 88 */
	OPCODE(0x89, o6_setClass);
	OPCODE(0x8a, o6_setState);
	OPCODE(0x8b, o6_setOwner);
	/* 8C */
	OPCODE(0x8c, o6_panCameraTo);
	OPCODE(0x8d, o6_actorFollowCamera);
	OPCODE(0x8e, o6_setCameraAt);
	OPCODE(0x8f, o6_printActor);
	/* 90 */
	OPCODE(0x90, o6_printEgo);
	OPCODE(0x91, o6_talkActor);
	OPCODE(0x92, o6_talkEgo);
	OPCODE(0x93, o6_printLine);
	/* 94 */
	OPCODE(0x94, o6_printText);
	OPCODE(0x95, o6_printDebug);
	OPCODE(0x96, o6_printSystem);
	OPCODE(0x97, o8_blastText);
	/* 98 */
	OPCODE(0x98, o8_drawObject);
	/* 9C */
	OPCODE(0x9c, o8_cursorCommand);
	OPCODE(0x9d, o6_loadRoom);
	OPCODE(0x9e, o6_loadRoomWithEgo);
	OPCODE(0x9f, o6_walkActorToObj);
	/* A0 */
	OPCODE(0xa0, o6_walkActorTo);
	OPCODE(0xa1, o6_putActorAtXY);
	OPCODE(0xa2, o6_putActorAtObject);
	OPCODE(0xa3, o6_faceActor);
	/* A4 */
	OPCODE(0xa4, o6_animateActor);
	OPCODE(0xa5, o6_doSentence);
	OPCODE(0xa6, o6_pickupObject);
	OPCODE(0xa7, o6_setBoxFlags);
	/* A8 */
	OPCODE(0xa8, o6_createBoxMatrix);
	OPCODE(0xaa, o8_resourceRoutines);
	OPCODE(0xab, o8_roomOps);
	/* AC */
	OPCODE(0xac, o8_actorOps);
	OPCODE(0xad, o8_cameraOps);
	OPCODE(0xae, o8_verbOps);
	OPCODE(0xaf, o6_startSound);
	/* B0 */
	OPCODE(0xb0, o6_startMusic);
	OPCODE(0xb1, o6_stopSound);
	OPCODE(0xb2, o6_soundKludge);
	OPCODE(0xb3, o8_systemOps);
	/* B4 */
	OPCODE(0xb4, o6_saveRestoreVerbs);
	OPCODE(0xb5, o6_setObjectName);
	OPCODE(0xb6, o6_getDateTime);
	OPCODE(0xb7, o6_drawBox);
	/* B8 */
	OPCODE(0xb9, o8_startVideo);
	OPCODE(0xba, o8_kernelSetFunctions);
	/* BC */
	/* C0 */
	/* C4 */
	/* C8 */
	OPCODE(0xc8, o6_startScriptQuick2);
	OPCODE(0xc9, o6_startObjectQuick);
	OPCODE(0xca, o6_pickOneOf);
	OPCODE(0xcb, o6_pickOneOfDefault);
	/* CC */
	OPCODE(0xcd, o6_isAnyOf);
	OPCODE(0xce, o6_getRandomNumber);
	OPCODE(0xcf, o6_getRandomNumberRange);
	/* D0 */
	OPCODE(0xd0, o6_ifClassOfIs);
	OPCODE(0xd1, o6_getState);
	OPCODE(0xd2, o6_getOwner);
	OPCODE(0xd3, o6_isScriptRunning);
	/* D4 */
	OPCODE(0xd5, o6_isSoundRunning);
	OPCODE(0xd6, o6_abs);
	/* D8 */
	OPCODE(0xd8, o8_kernelGetFunctions);
	OPCODE(0xd9, o6_isActorInBox);
	OPCODE(0xda, o6_getVerbEntrypoint);
	OPCODE(0xdb, o6_getActorFromXY);
	/* DC */
	OPCODE(0xdc, o6_findObject);
	OPCODE(0xdd, o6_getVerbFromXY);
	OPCODE(0xdf, o6_findInventory);
	/* E0 */
	OPCODE(0xe0, o6_getInventoryCount);
	OPCODE(0xe1, o6_getAnimateVariable);
	OPCODE(0xe2, o6_getActorRoom);
	OPCODE(0xe3, o6_getActorWalkBox);
	/* E4 */
	OPCODE(0xe4, o6_getActorMoving);
	OPCODE(0xe5, o6_getActorCostume);
	OPCODE(0xe6, o6_getActorScaleX);
	OPCODE(0xe7, o6_getActorLayer);
	/* E8 */
	OPCODE(0xe8, o6_getActorElevation);
	OPCODE(0xe9, o6_getActorWidth);
	OPCODE(0xea, o6_getObjectNewDir);
	OPCODE(0xeb, o6_getObjectX);
	/* EC */
	OPCODE(0xec, o6_getObjectY);
	OPCODE(0xed, o8_getActorChore);
	OPCODE(0xee, o6_distObjectObject);
	OPCODE(0xef, o6_distPtPt);
	/* F0 */
	OPCODE(0xf0, o8_getObjectImageX);
	OPCODE(0xf1, o8_getObjectImageY);
	OPCODE(0xf2, o8_getObjectImageWidth);
	OPCODE(0xf3, o8_getObjectImageHeight);
	/* F4 */
	OPCODE(0xf6, o8_getStringWidth);
	OPCODE(0xf7, o8_getActorZPlane);
	/* F8 */
	/* FC */
}

// In V8, the word size is 4 byte, not 2 bytes as in V6/V7 games
uint ScummEngine_v8::fetchScriptWord() {
	return fetchScriptDWord();
}

int ScummEngine_v8::fetchScriptWordSigned() {
	return (int32)fetchScriptDWordSigned();
}

int ScummEngine_v8::readVar(uint var) {
	debugC(DEBUG_VARS, "readvar(%d)", var);

	if (!(var & 0xF0000000)) {
		assertRange(0, var, _numVariables - 1, "variable");
		return _scummVars[var];
	}

	if (var & 0x80000000) {
		var &= 0x7FFFFFFF;
		assertRange(0, var, _numBitVariables - 1, "bit variable (reading)");
		return (_bitVars[var >> 3] & (1 << (var & 7))) ? 1 : 0;
	}

	if (var & 0x40000000) {
		var &= 0xFFFFFFF;
		assertRange(0, var, 25, "local variable (reading)");
		return vm.localvar[_currentScript][var];
	}

	error("Illegal varbits (r)");
	return -1;
}

void ScummEngine_v8::writeVar(uint var, int value) {
	debugC(DEBUG_VARS, "writeVar(%d, %d)", var, value);

	if (!(var & 0xF0000000)) {
		assertRange(0, var, _numVariables - 1, "variable (writing)");

		if (var == VAR_CHARINC) {
			// Did the user override the talkspeed manually? Then use that.
			// Otherwise, use the value specified by the game script.
			// Note: To determine whether there was a user override, we only
			// look at the target specific settings, assuming that any global
			// value is likely to be bogus. See also bug #2251765.
			if (ConfMan.hasKey("talkspeed", _targetName)) {
				value = getTalkSpeed();
			} else {
				// Save the new talkspeed value to ConfMan
				setTalkSpeed(value);
			}
		}

		_scummVars[var] = value;

		if ((_varwatch == (int)var) || (_varwatch == 0)) {
			if (vm.slot[_currentScript].number < 100)
				debugC(DEBUG_VARS, "vars[%d] = %d (via script-%d)", var, value, vm.slot[_currentScript].number);
			else
				debugC(DEBUG_VARS, "vars[%d] = %d (via room-%d-%d)", var, value, _currentRoom, vm.slot[_currentScript].number);
		}
		return;
	}

	if (var & 0x80000000) {
		var &= 0x7FFFFFFF;
		assertRange(0, var, _numBitVariables - 1, "bit variable (writing)");

		if (value)
			_bitVars[var >> 3] |= (1 << (var & 7));
		else
			_bitVars[var >> 3] &= ~(1 << (var & 7));
		return;
	}

	if (var & 0x40000000) {
		var &= 0xFFFFFFF;
		assertRange(0, var, 25, "local variable (writing)");
		vm.localvar[_currentScript][var] = value;
		return;
	}

	error("Illegal varbits (w)");
}

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

	switch (b) {
	case 0xC8:		// SO_PRINT_BASEOP
		_string[m].loadDefault();
		if (n)
			_actorToPrintStrFor = pop();
		break;
	case 0xC9:		// SO_PRINT_END
		_string[m].saveDefault();
		break;
	case 0xCA:		// SO_PRINT_AT
		_string[m].ypos = pop();
		_string[m].xpos = pop();
		_string[m].overhead = false;
		break;
	case 0xCB:		// SO_PRINT_COLOR
		_string[m].color = pop();
		break;
	case 0xCC:		// SO_PRINT_CENTER
		_string[m].center = true;
		_string[m].overhead = false;
		break;
	case 0xCD:		// SO_PRINT_CHARSET Set print character set
		_string[m].charset = pop();
		break;
	case 0xCE:		// SO_PRINT_LEFT
		_string[m].wrapping = false;
		_string[m].overhead = false;
		break;
	case 0xCF:		// SO_PRINT_OVERHEAD
		_string[m].overhead = true;
		_string[m].no_talk_anim = false;
		break;
	case 0xD0:		// SO_PRINT_MUMBLE
		_string[m].no_talk_anim = true;
		break;
	case 0xD1:		// SO_PRINT_STRING
		printString(m, _scriptPointer);
		_scriptPointer += resStrLen(_scriptPointer) + 1;
		break;
	case 0xD2:		// SO_PRINT_WRAP Set print wordwrap
		_string[m].wrapping = true;
		_string[m].overhead = false;
		break;
	default:
		error("decodeParseString: default case 0x%x", b);
	}
}

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

	while ((num = _fileHandle->readUint32LE()) != 0) {
		a = _fileHandle->readUint32LE();
		b = _fileHandle->readUint32LE();

		if (b != 0)
			defineArray(num, kIntArray, b, a);
		else
			defineArray(num, kIntArray, a, b);
	}
}

void ScummEngine_v8::o8_mod() {
	int a = pop();
	push(pop() % a);
}

void ScummEngine_v8::o8_wait() {
	int actnum;
	int offs = -2;
	Actor *a;
	byte subOp = fetchScriptByte();

	switch (subOp) {
	case 0x1E:		// SO_WAIT_FOR_ACTOR Wait for actor (to finish current action?)
		offs = fetchScriptWordSigned();
		actnum = pop();
		a = derefActor(actnum, "o8_wait:SO_WAIT_FOR_ACTOR");
		if (a->isInCurrentRoom() && a->_moving)
			break;
		return;
	case 0x1F:		// SO_WAIT_FOR_MESSAGE Wait for message
		if (VAR(VAR_HAVE_MSG))
			break;
		return;
	case 0x20:		// SO_WAIT_FOR_CAMERA Wait for camera (to finish current action?)
		if (camera._dest != camera._cur)
			break;
		return;
	case 0x21:		// 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 0x22:		// SO_WAIT_FOR_ANIMATION
		offs = fetchScriptWordSigned();
		actnum = pop();
		a = derefActor(actnum, "o8_wait:SO_WAIT_FOR_ANIMATION");
		if (a->isInCurrentRoom() && a->_needRedraw)
			break;
		return;
	case 0x23:		// SO_WAIT_FOR_TURN
		offs = fetchScriptWordSigned();
		actnum = pop();
		a = derefActor(actnum, "o8_wait:SO_WAIT_FOR_TURN");
		if (a->isInCurrentRoom() && a->_moving & MF_TURN)
			break;
		return;
	default:
		error("o8_wait: default case 0x%x", subOp);
	}

	_scriptPointer += offs;
	o6_breakHere();
}

void ScummEngine_v8::o8_dimArray() {
	byte subOp = fetchScriptByte();
	int array = fetchScriptWord();

	switch (subOp) {
	case 0x0A:		// SO_ARRAY_SCUMMVAR
		defineArray(array, kIntArray, 0, pop());
		break;
	case 0x0B:		// SO_ARRAY_STRING
		defineArray(array, kStringArray, 0, pop());
		break;
	case 0x0C:		// SO_ARRAY_UNDIM
		nukeArray(array);
		break;
	default:
		error("o8_dimArray: default case 0x%x", subOp);
	}
}

void ScummEngine_v8::o8_dim2dimArray() {
	byte subOp = fetchScriptByte();
	int array = fetchScriptWord(), a, b;

	switch (subOp) {
	case 0x0A:		// SO_ARRAY_SCUMMVAR
		b = pop();
		a = pop();
		defineArray(array, kIntArray, a, b);
		break;
	case 0x0B:		// SO_ARRAY_STRING
		b = pop();
		a = pop();
		defineArray(array, kStringArray, a, b);
		break;
	case 0x0C:		// SO_ARRAY_UNDIM
		nukeArray(array);
		break;
	default:
		error("o8_dim2dimArray: default case 0x%x", subOp);
	}
}

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

	switch (subOp) {
	case 0x14:		// SO_ASSIGN_STRING
		b = pop();
		len = resStrLen(_scriptPointer);
		data = defineArray(array, kStringArray, 0, len + 1);
		copyScriptString(data + b);
		break;
	case 0x15:		// SO_ASSIGN_SCUMMVAR_LIST
		b = pop();
		len = getStackList(list, ARRAYSIZE(list));
		d = readVar(array);
		if (d == 0) {
			defineArray(array, kIntArray, 0, b + len);
		}
		while (--len >= 0) {
			writeArray(array, 0, b + len, list[len]);
		}
		break;
	case 0x16:		// 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("o8_arrayOps: default case 0x%x (array %d)", subOp, array);
	}
}

void ScummEngine_v8::o8_blastText() {
	// Original V8 interpreter uses StringSlot 2 for o_blastText and 4 for o_printDebug.
	// Since slot 2 is already mapped to printDebug for V6 (see ScummEngine::printString()),
	// we just "swap" the slots, and use slot 4 here.
	decodeParseString(4, 0);
}

void ScummEngine_v8::o8_cursorCommand() {
	byte subOp = fetchScriptByte();
	int a;
	int args[4];

	switch (subOp) {
	case 0xDC:		// SO_CURSOR_ON Turn cursor on
		_cursor.state = 1;
		verbMouseOver(0);
		break;
	case 0xDD:		// SO_CURSOR_OFF Turn cursor off
		_cursor.state = 0;
		verbMouseOver(0);
		break;
	case 0xDE:		// SO_CURSOR_SOFT_ON Turn soft cursor on
		_cursor.state++;
		verbMouseOver(0);
		break;
	case 0xDF:		// SO_CURSOR_SOFT_OFF Turn soft cursor off
		_cursor.state--;
		verbMouseOver(0);
		break;
	case 0xE0:		// SO_USERPUT_ON
		_userPut = 1;
		break;
	case 0xE1:		// SO_USERPUT_OFF
		_userPut = 0;
		break;
	case 0xE2:		// SO_USERPUT_SOFT_ON
		_userPut++;
		break;
	case 0xE3:		// SO_USERPUT_SOFT_OFF
		_userPut--;
		break;
	case 0xE4:		// SO_CURSOR_IMAGE Set cursor image
		{
			int idx = pop();
			int room, obj;
			obj = popRoomAndObj(&room);
			setCursorFromImg(obj, room, idx);
		}
		break;
	case 0xE5:		// SO_CURSOR_HOTSPOT Set cursor hotspot
		a = pop();
		setCursorHotspot(pop(), a);
		break;
	case 0xE6:		// SO_CURSOR_TRANSPARENT Set cursor transparent color
		setCursorTransparency(pop());
		break;
	case 0xE7:		// SO_CHARSET_SET
		_string[0]._default.charset = pop();
		break;
	case 0xE8:		// SO_CHARSET_COLOR
		getStackList(args, ARRAYSIZE(args));
		// This opcode does nothing (confirmed with disasm)
		break;
	case 0xE9:		// SO_CURSOR_PUT
		{
		int y = pop();
		int x = pop();

		_system->warpMouse(x, y);
		}
		break;
	default:
		error("o8_cursorCommand: default case 0x%x", subOp);
	}

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

void ScummEngine_v8::o8_resourceRoutines() {
	byte subOp = fetchScriptByte();
	int resid = pop();

	switch (subOp) {
	case 0x3C:		// Dummy case
		break;
	case 0x3D:		// SO_HEAP_LOAD_COSTUME Load costume to heap
		ensureResourceLoaded(rtCostume, resid);
		break;
	case 0x3E:		// SO_HEAP_LOAD_OBJECT Load object to heap
		{
		int room = getObjectRoom(resid);
		loadFlObject(resid, room);
		}
		break;
	case 0x3F:		// SO_HEAP_LOAD_ROOM Load room to heap
		ensureResourceLoaded(rtRoom, resid);
		break;
	case 0x40:		// SO_HEAP_LOAD_SCRIPT Load script to heap
		ensureResourceLoaded(rtScript, resid);
		break;
	case 0x41:		// SO_HEAP_LOAD_SOUND Load sound to heap
		ensureResourceLoaded(rtSound, resid);
		break;

	case 0x42:		// SO_HEAP_LOCK_COSTUME Lock costume in heap
		_res->lock(rtCostume, resid);
		break;
	case 0x43:		// SO_HEAP_LOCK_ROOM Lock room in heap
		_res->lock(rtRoom, resid);
		break;
	case 0x44:		// SO_HEAP_LOCK_SCRIPT Lock script in heap
		_res->lock(rtScript, resid);
		break;
	case 0x45:		// SO_HEAP_LOCK_SOUND Lock sound in heap
		_res->lock(rtSound, resid);
		break;
	case 0x46:		// SO_HEAP_UNLOCK_COSTUME Unlock costume
		_res->unlock(rtCostume, resid);
		break;
	case 0x47:		// SO_HEAP_UNLOCK_ROOM Unlock room
		_res->unlock(rtRoom, resid);
		break;
	case 0x48:		// SO_HEAP_UNLOCK_SCRIPT Unlock script
		_res->unlock(rtScript, resid);
		break;
	case 0x49:		// SO_HEAP_UNLOCK_SOUND Unlock sound
		_res->unlock(rtSound, resid);
		break;
	case 0x4A:		// SO_HEAP_NUKE_COSTUME Remove costume from heap
		_res->setResourceCounter(rtCostume, resid, 0x7F);
		break;
	case 0x4B:		// SO_HEAP_NUKE_ROOM Remove room from heap
		_res->setResourceCounter(rtRoom, resid, 0x7F);
		break;
	case 0x4C:		// SO_HEAP_NUKE_SCRIPT Remove script from heap
		_res->setResourceCounter(rtScript, resid, 0x7F);
		break;
	case 0x4D:		// SO_HEAP_NUKE_SOUND Remove sound from heap
		_res->setResourceCounter(rtSound, resid, 0x7F);
		break;
	default:
		error("o8_resourceRoutines: default case 0x%x", subOp);
	}
}

void ScummEngine_v8::o8_roomOps() {
	byte subOp = fetchScriptByte();
	int a, b, c, d, e;

	switch (subOp) {
	case 0x52:		// SO_ROOM_PALETTE Set room palette
		d = pop();
		c = pop();
		b = pop();
		a = pop();
		setPalColor(d, a, b, c);
		break;
	case 0x57:		// SO_ROOM_FADE Fade room
		a = pop();
		if (a) {
			_switchRoomEffect = (byte)(a);
			_switchRoomEffect2 = (byte)(a >> 8);
		} else {
			fadeIn(_newEffect);
		}
		break;
	case 0x58:		// SO_ROOM_RGB_INTENSITY Set room color intensity
		e = pop();
		d = pop();
		c = pop();
		b = pop();
		a = pop();
		darkenPalette(a, b, c, d, e);
		break;
	case 0x59:		// SO_ROOM_TRANSFORM Transform room
		d = pop();
		c = pop();
		b = pop();
		a = pop();
		palManipulateInit(a, b, c, d);
		break;
	case 0x5C:		// SO_ROOM_NEW_PALETTE New palette
		a = pop();
		setCurrentPalette(a);
		break;
	case 0x5D:		// SO_ROOM_SAVE_GAME Save game
		_saveSound = 0;
		_saveTemporaryState = true;
		_saveLoadSlot = 1;
		_saveLoadFlag = 1;
		break;
	case 0x5E:		// SO_ROOM_LOAD_GAME Load game
		_saveSound = pop();
		if (!_saveLoadFlag) {
			_saveTemporaryState = true;
			_saveLoadSlot = 1;
			_saveLoadFlag = 2;
		}
		break;
	case 0x5F:		// SO_ROOM_SATURATION Set saturation of room colors
		e = pop();
		d = pop();
		c = pop();
		b = pop();
		a = pop();
		desaturatePalette(a, b, c, d, e);
		break;
	default:
		error("o8_roomOps: default case 0x%x", subOp);
	}
}

void ScummEngine_v8::o8_actorOps() {
	byte subOp = fetchScriptByte();
	Actor *a;
	int i, j;

	if (subOp == 0x7A) {
		_curActor = pop();
		return;
	}

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

	switch (subOp) {
	case 0x64:		// SO_ACTOR_COSTUME Set actor costume
		a->setActorCostume(pop());
		break;
	case 0x65:		// SO_ACTOR_STEP_DIST Set actor width of steps
		j = pop();
		i = pop();
		a->setActorWalkSpeed(i, j);
		break;
	case 0x67:		// SO_ACTOR_ANIMATION_DEFAULT Set actor animation to default
		a->_initFrame = 1;
		a->_walkFrame = 2;
		a->_standFrame = 3;
		a->_talkStartFrame = 4;
		a->_talkStopFrame = 5;
		break;
	case 0x68:		// SO_ACTOR_ANIMATION_INIT Initialize animation
		a->_initFrame = pop();
		break;
	case 0x69:		// SO_ACTOR_ANIMATION_TALK Set actor animation to talk animation
		a->_talkStopFrame = pop();
		a->_talkStartFrame = pop();
		break;
	case 0x6A:		// SO_ACTOR_ANIMATION_WALK Set actor animation to walk animation
		a->_walkFrame = pop();
		break;
	case 0x6B:		// SO_ACTOR_ANIMATION_STAND Set actor animation to standing animation
		a->_standFrame = pop();
		break;
	case 0x6C:		// SO_ACTOR_ANIMATION_SPEED Set speed of animation
		a->setAnimSpeed(pop());
		break;
	case 0x6D:		// SO_ACTOR_DEFAULT
		a->initActor(0);
		break;
	case 0x6E:		// SO_ACTOR_ELEVATION
		a->setElevation(pop());
		break;
	case 0x6F:		// SO_ACTOR_PALETTE Set actor palette
		j = pop();
		i = pop();
		assertRange(0, i, 31, "o8_actorOps: palette slot");
		a->setPalette(i, j);
		break;
	case 0x70:		// SO_ACTOR_TALK_COLOR Set actor talk color
		a->_talkColor = pop();
		break;
	case 0x71:		// SO_ACTOR_NAME Set name of actor
		loadPtrToResource(rtActorName, a->_number, NULL);
		break;
	case 0x72:		// SO_ACTOR_WIDTH Set width of actor
		a->_width = pop();
		break;
	case 0x73:		// SO_ACTOR_SCALE Set scaling of actor
		i = pop();
		a->setScale(i, i);
		break;
	case 0x74:		// SO_ACTOR_NEVER_ZCLIP
		a->_forceClip = 0;
		break;
	case 0x75:		// SO_ACTOR_ALWAYS_ZCLIP
		a->_forceClip = pop();
		// V8 uses 255 where we used to use 100
		if (a->_forceClip == 255)
			a->_forceClip = 100;
		break;
	case 0x76:		// SO_ACTOR_IGNORE_BOXES Make actor ignore boxes
		a->_ignoreBoxes = true;
		a->_forceClip = 100;
		if (a->isInCurrentRoom())
			a->putActor();
		break;
	case 0x77:		// SO_ACTOR_FOLLOW_BOXES Make actor follow boxes
		a->_ignoreBoxes = false;
		a->_forceClip = 100;
		if (a->isInCurrentRoom())
			a->putActor();
		break;
	case 0x78:		// SO_ACTOR_SPECIAL_DRAW
		a->_shadowMode = pop();
		break;
	case 0x79:		// SO_ACTOR_TEXT_OFFSET Set text offset relative to actor
		a->_talkPosY = pop();
		a->_talkPosX = pop();
		break;
//	case 0x7A:		// SO_ACTOR_INIT Set current actor (handled above)
	case 0x7B:		// SO_ACTOR_VARIABLE Set actor variable
		i = pop();
		a->setAnimVar(pop(), i);
		break;
	case 0x7C:		// SO_ACTOR_IGNORE_TURNS_ON Make actor ignore turns
		a->_ignoreTurns = true;
		break;
	case 0x7D:		// SO_ACTOR_IGNORE_TURNS_OFF Make actor follow turns
		a->_ignoreTurns = false;
		break;
	case 0x7E:		// SO_ACTOR_NEW New actor
		a->initActor(2);
		break;
	case 0x7F:		// SO_ACTOR_DEPTH Set actor Z position
		a->_layer = pop();
		break;
	case 0x80:		// SO_ACTOR_STOP
		a->stopActorMoving();
		a->startAnimActor(a->_standFrame);
		break;
	case 0x81:		// SO_ACTOR_FACE Make actor face angle
		a->_moving &= ~MF_TURN;
		a->setDirection(pop());
		break;
	case 0x82:		// SO_ACTOR_TURN Turn actor
		a->turnToDirection(pop());
		break;
	case 0x83:		// SO_ACTOR_WALK_SCRIPT Set walk script for actor?
		a->_walkScript = pop();
		break;
	case 0x84:		// SO_ACTOR_TALK_SCRIPT Set talk script for actor?
		a->_talkScript = pop();
		break;
	case 0x85:		// SO_ACTOR_WALK_PAUSE
		a->_moving |= MF_FROZEN;
		break;
	case 0x86:		// SO_ACTOR_WALK_RESUME
		a->_moving &= ~MF_FROZEN;
		break;
	case 0x87:		// SO_ACTOR_VOLUME Set volume of actor speech
		a->_talkVolume = pop();
		break;
	case 0x88:		// SO_ACTOR_FREQUENCY Set frequency of actor speech
		a->_talkFrequency = pop();
		break;
	case 0x89:		// SO_ACTOR_PAN
		a->_talkPan = pop();
		break;
	default:
		error("o8_actorOps: default case 0x%x", subOp);
	}
}

void ScummEngine_v8::o8_cameraOps() {
	byte subOp = fetchScriptByte();

	switch (subOp) {
	case 0x32:		// SO_CAMERA_PAUSE
		//debug(0, "freezeCamera NYI");
		break;
	case 0x33:		// SO_CAMERA_RESUME
		//debug(0, "unfreezeCamera NYI");
		break;
	default:
		error("o8_cameraOps: default case 0x%x", subOp);
	}
}

void ScummEngine_v8::o8_verbOps() {
	byte subOp = fetchScriptByte();
	VerbSlot *vs = NULL;
	int slot, a, b;

	if (subOp == 0x96) {
		_curVerb = pop();
		_curVerbSlot = getVerbSlot(_curVerb, 0);
		assertRange(0, _curVerbSlot, _numVerbs - 1, "new verb slot");
		return;
	}

	assert(0 <= _curVerbSlot && _curVerbSlot < _numVerbs);
	vs = &_verbs[_curVerbSlot];
	assert(vs);

	switch (subOp) {
	case 0x96:		// SO_VERB_INIT Choose verb number for editing
		// handled above!
		break;
	case 0x97:		// SO_VERB_NEW New verb
		if (_curVerbSlot == 0) {
			for (slot = 1; slot < _numVerbs; slot++) {
				if (_verbs[slot].verbid == 0)
					break;
			}
			if (slot >= _numVerbs) {
				error("Too many verbs");
			}
			_curVerbSlot = slot;
		}
		vs = &_verbs[_curVerbSlot];
		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 0x98:		// SO_VERB_DELETE Delete verb
		killVerb(_curVerbSlot);
		break;
	case 0x99:		// SO_VERB_NAME Set verb name
		loadPtrToResource(rtVerb, _curVerbSlot, NULL);
		vs->type = kTextVerbType;
		vs->imgindex = 0;
		break;
	case 0x9A:		// SO_VERB_AT Set verb (X,Y) placement
		vs->curRect.top = pop();
		vs->curRect.left = pop();
		break;
	case 0x9B:		// SO_VERB_ON Turn verb on
		vs->curmode = 1;
		break;
	case 0x9C:		// SO_VERB_OFF Turn verb off
		vs->curmode = 0;
		break;
	case 0x9D:		// SO_VERB_COLOR Set verb color
		vs->color = pop();
		break;
	case 0x9E:		// SO_VERB_HICOLOR Set verb highlighted color
		vs->hicolor = pop();
		break;
	case 0xA0:		// SO_VERB_DIMCOLOR Set verb dimmed (disabled) color
		vs->dimcolor = pop();
		break;
	case 0xA1:		// SO_VERB_DIM
		vs->curmode = 2;
		break;
	case 0xA2:		// SO_VERB_KEY Set keypress to associate with verb
		vs->key = pop();
		break;
	case 0xA3:		// SO_VERB_IMAGE Set verb image
		b = pop();
		a = pop();
		if (_curVerbSlot && a != vs->imgindex) {
			setVerbObject(b, a, _curVerbSlot);
			vs->type = kImageVerbType;
			vs->imgindex = a;
		}
		break;
	case 0xA4:		// SO_VERB_NAME_STR Set verb name
		a = pop();
		if (a == 0) {
			loadPtrToResource(rtVerb, _curVerbSlot, (const byte *)"");
		} else {
			loadPtrToResource(rtVerb, _curVerbSlot, getStringAddress(a));
		}
		vs->type = kTextVerbType;
		vs->imgindex = 0;
		break;
	case 0xA5:		// SO_VERB_CENTER Center verb
		vs->center = 1;
		break;
	case 0xA6:		// SO_VERB_CHARSET Choose charset for verb
		vs->charset_nr = pop();
		break;
	case 0xA7:		// SO_VERB_LINE_SPACING Choose linespacing for verb
		_verbLineSpacing = pop();
		break;
	default:
		error("o8_verbops: default case 0x%x", subOp);
	}
}

void ScummEngine_v8::o8_systemOps() {
	byte subOp = fetchScriptByte();
	switch (subOp) {
	case 0x28:		// SO_SYSTEM_RESTART Restart game
		restart();
		break;
	case 0x29:		// SO_SYSTEM_QUIT Quit game
		quitGame();
		break;
	default:
		error("o8_systemOps: invalid case 0x%x", subOp);
	}
}


void ScummEngine_v8::o8_startVideo() {
	int len = resStrLen(_scriptPointer);

	_splayer->play((const char*)_scriptPointer, 12);

	_scriptPointer += len + 1;
}

void ScummEngine_v8::o8_kernelSetFunctions() {
	// TODO
	Actor *a;
	int args[30];
	int len = getStackList(args, ARRAYSIZE(args));

	switch (args[0]) {
	case 11: {	// lockObject
		int objidx = getObjectIndex(args[1]);
		assert(objidx != -1);
		_res->lock(rtFlObject, _objs[objidx].fl_object_index);
		break;
	}
	case 12: {	// unlockObject
		int objidx = getObjectIndex(args[1]);
		assert(objidx != -1);
		_res->unlock(rtFlObject, _objs[objidx].fl_object_index);
		break;
	}
	case 13:	// remapCostume
		a = derefActor(args[1], "o8_kernelSetFunctions:remapCostume");
		a->remapActorPalette(args[2], args[3], args[4], -1);
		break;
	case 14:	// remapCostumeInsert
		a = derefActor(args[1], "o8_kernelSetFunctions:remapCostumeInsert");
		a->remapActorPalette(args[2], args[3], args[4], args[5]);
		break;
	case 15:	// setVideoFrameRate
		// not used anymore (was smush frame rate)
		break;
	case 20:	// setBoxScaleSlot
		setBoxScaleSlot(args[1], args[2]);
		break;
	case 21:	// setScaleSlot
		setScaleSlot(args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
		break;
	case 22:	// setBannerColors
//		debug(0, "o8_kernelSetFunctions: setBannerColors(%d, %d, %d, %d)", args[1], args[2], args[3], args[4]);
		break;
	case 23:	// setActorChoreLimbFrame
		a = derefActor(args[1], "o8_kernelSetFunctions:setActorChoreLimbFrame");

		a->startAnimActor(args[2]);
		a->animateLimb(args[3], args[4]);
		break;
	case 24:	// clearTextQueue
		removeBlastTexts();
		break;
	case 25: {	// saveGameReadName
		Common::String name;
		if (getSavegameName(args[1], name)) {
			int size = name.size() + 1;
			_res->nukeResource(rtString, args[2]);

			ArrayHeader *ah = (ArrayHeader *)_res->createResource(rtString, args[2], size + sizeof(ArrayHeader));
			ah->type = TO_LE_16(kStringArray);
			ah->dim1 = TO_LE_16(size + 1);
			ah->dim2 = TO_LE_16(1);

			memcpy(getStringAddress(args[2]), name.c_str(), size);
		}
		break;
	}
	case 26: { // saveGameWrite
		// FIXME: This doesn't work
		char *address = (char *)getStringAddress(args[2]);
		debug(0, "o8_kernelSetFunctions: saveGame(%d, %s)", args[1], address);
		break;
	}
	case 27: // saveGameRead
		_saveLoadSlot = args[1];
		_saveLoadFlag = 2;
		_saveTemporaryState = false;
		break;
	case 28:	// saveGameStampScreenshot
		debug(0, "o8_kernelSetFunctions: saveGameStampScreenshot(%d, %d, %d, %d, %d, %d)", args[1], args[2], args[3], args[4], args[5], args[6]);
		break;
	case 29:	// setKeyScript
		_keyScriptKey = args[1];
		_keyScriptNo = args[2];
		break;
	case 30:	// killAllScriptsButMe
		killAllScriptsExceptCurrent();
		break;
	case 31:	// stopAllVideo
		debug(0, "o8_kernelSetFunctions: stopAllVideo()");
		break;
	case 32:	// writeRegistryValue
		{
		int idx = args[1];
		int value = args[2];
		const char *str = (const char *)getStringAddress(idx);

		debugC(DEBUG_GENERAL,"o8_kernelSetFunctions: writeRegistryValue(%s, %d)", str, value);
		}
		break;
	case 33:	// paletteSetIntensity
		debug(0, "o8_kernelSetFunctions: paletteSetIntensity(%d, %d)", args[1], args[2]);
		break;
	case 34:	// queryQuit
		if (ConfMan.getBool("confirm_exit"))
			confirmExitDialog();
		else
			quitGame();
		break;
	case 108:	// buildPaletteShadow
		setShadowPalette(args[1], args[2], args[3], args[4], args[5], args[6]);
		break;
	case 109:	// setPaletteShadow
		setShadowPalette(0, args[1], args[2], args[3], args[4], args[5]);
		break;
	case 118:	// blastShadowObject
		enqueueObject(args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], 3);
		break;
	case 119:	// superBlastObject
		enqueueObject(args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], 0);
		break;

	default:
		error("o8_kernelSetFunctions: default case 0x%x (len = %d)", args[0], len);
	}
}

void ScummEngine_v8::o8_kernelGetFunctions() {
	int args[30];
	int len = getStackList(args, ARRAYSIZE(args));

	switch (args[0]) {
	case 0x73:	// getWalkBoxAt
		push(getSpecialBox(args[1], args[2]));
		break;
	case 0x74:	// isPointInBox
		push(checkXYInBoxBounds(args[3], args[1], args[2]));
		break;
	case 0xD3:		// getKeyState
		push(getKeyState(args[1]));
		break;
	case 0xCE:		// getRGBSlot
		push(remapPaletteColor(args[1], args[2], args[3], -1));
		break;
	case 0xD7:		// getBox
		push(checkXYInBoxBounds(args[3], args[1], args[2]));
		break;
	case 0xD8: {		// findBlastObject
		int x = args[1] + (camera._cur.x & 7);
		int y = args[2] + _screenTop;
		BlastObject *eo;

		for (int i = _blastObjectQueuePos - 1; i >= 0; i--) {
			eo = &_blastObjectQueue[i];

			if (eo->rect.contains(x, y) && !getClass(eo->number, kObjectClassUntouchable)) {
				push(eo->number);
				return;
			}
		}
		push(0);
		break;
	}
	case 0xD9: {   // actorHit - used, for example, to detect ship collision
	               // during ship-to-ship combat.
		Actor *a = derefActor(args[1], "actorHit");
		push(a->actorHitTest(args[2], args[3] + _screenTop));
		break;
	}
	case 0xDA:		// lipSyncWidth
		push(_imuseDigital->getCurVoiceLipSyncWidth());
		break;
	case 0xDB:		// lipSyncHeight
		push(_imuseDigital->getCurVoiceLipSyncHeight());
		break;
	case 0xDC:		// actorTalkAnimation
		{
		Actor *a = derefActor(args[1], "actorTalkAnimation");
		push(a->_talkStartFrame);
		}
		break;
	case 0xDD:		// getGroupSfxVol
		push(_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 2);
		break;
	case 0xDE:		// getGroupVoiceVol
		push(_mixer->getVolumeForSoundType(Audio::Mixer::kSpeechSoundType) / 2);
		break;
	case 0xDF:		// getGroupMusicVol
		push(_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 2);
		break;
	case 0xE0:		// readRegistryValue
		{
		int idx = args[1];
		const char *str = (const char *)getStringAddress(idx);
		if (!strcmp(str, "SFX Volume"))
			push(ConfMan.getInt("sfx_volume") / 2);
		else if (!strcmp(str, "Voice Volume"))
			push(ConfMan.getInt("speech_volume") / 2);
		else if (!strcmp(str, "Music Volume"))
			push(ConfMan.getInt("music_volume") / 2);
		else if (!strcmp(str, "Text Status"))
			push(ConfMan.getBool("subtitles"));
		else if (!strcmp(str, "Object Names"))
			push(ConfMan.getBool("object_labels"));
		else if (!strcmp(str, "Saveload Page"))
			push(14);
		else		// Use defaults
			push(-1);
		debugC(DEBUG_GENERAL,"o8_kernelGetFunctions: readRegistryValue(%s)", str);
		}
		break;
	case 0xE1:		// imGetMusicPosition
		push(_imuseDigital->getCurMusicPosInMs());
		break;
	case 0xE2:		// musicLipSyncWidth
		push(_imuseDigital->getCurMusicLipSyncWidth(args[1]));
		break;
	case 0xE3:		// musicLipSyncHeight
		push(_imuseDigital->getCurMusicLipSyncHeight(args[1]));
		break;
	default:
		error("o8_kernelGetFunctions: default case 0x%x (len = %d)", args[0], len);
	}

}

void ScummEngine_v8::o8_getActorChore() {
	int actnum = pop();
	Actor *a = derefActor(actnum, "o8_getActorChore");
	push(a->_frame);
}

void ScummEngine_v8::o8_getActorZPlane() {
	int actnum = pop();
	Actor *a = derefActor(actnum, "o8_getActorZPlane");

	int z = a->_forceClip;
	if (z == 100) {
		z = getMaskFromBox(a->_walkbox);
		if (z > _gdi->_numZBuffer - 1)
			z = _gdi->_numZBuffer - 1;
	}

	push(z);
}


void ScummEngine_v8::o8_getObjectImageX() {
	int i = getObjectIndex(pop());
	assert(i);
	push(_objs[i].x_pos);
}

void ScummEngine_v8::o8_getObjectImageY() {
	int i = getObjectIndex(pop());
	assert(i);
	push(_objs[i].y_pos);
}

void ScummEngine_v8::o8_getObjectImageWidth() {
	int i = getObjectIndex(pop());
	assert(i);
	push(_objs[i].width);
}

void ScummEngine_v8::o8_getObjectImageHeight() {
	int i = getObjectIndex(pop());
	assert(i);
	push(_objs[i].height);
}

void ScummEngine_v8::o8_getStringWidth() {
	int charset = pop();
	int oldID = _charset->getCurID();
	int width;
	const byte *msg = _scriptPointer;
	byte transBuf[512];

	// Skip to the next instruction
	_scriptPointer += resStrLen(_scriptPointer) + 1;

	translateText(msg, transBuf);
	msg = transBuf;

	// Temporary set the specified charset id
	_charset->setCurID(charset);
	// Determine the strings width
	width = _charset->getStringWidth(0, msg);
	// Revert to old font
	_charset->setCurID(oldID);

	push(width);
}

void ScummEngine_v8::o8_drawObject() {
	int state = pop();
	int y = pop();
	int x = pop();
	int obj = pop();
	setObjectState(obj, state, x, y);
}

} // End of namespace Scumm
