/* 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "asylum/resources/script.h"

#include "asylum/puzzles/puzzles.h"

#include "asylum/resources/actor.h"
#include "asylum/resources/encounters.h"
#include "asylum/resources/object.h"
#include "asylum/resources/worldstats.h"

#include "asylum/system/cursor.h"
#include "asylum/system/graphics.h"
#include "asylum/system/screen.h"
#include "asylum/system/speech.h"

#include "asylum/views/menu.h"
#include "asylum/views/scene.h"

#include "asylum/asylum.h"
#include "asylum/staticres.h"

namespace Asylum {

//////////////////////////////////////////////////////////////////////////
// ActionArea
//////////////////////////////////////////////////////////////////////////
void ActionArea::load(Common::SeekableReadStream *stream) {
	stream->read(&name, sizeof(name));
	id             = stream->readSint32LE();
	field01        = stream->readSint32LE();
	field02        = stream->readSint32LE();
	field_40       = stream->readSint32LE();
	field_44       = stream->readSint32LE();
	flags          = stream->readSint32LE();
	scriptIndex    = stream->readSint32LE();
	scriptIndex2   = stream->readSint32LE();
	actionType     = stream->readSint32LE();

	for (int32 i = 0; i < ARRAYSIZE(flagNums); i++)
		flagNums[i] = stream->readSint32LE();

	field_7C             = stream->readSint32LE();
	polygonIndex         = stream->readUint32LE();
	soundResourceIdFrame = (ResourceId)stream->readSint32LE();
	field_88             = stream->readSint32LE();
	soundResourceId      = (ResourceId)stream->readSint32LE();
	field_90             = stream->readSint32LE();
	paletteResourceId    = (ResourceId)stream->readSint32LE();

	for (int32 i = 0; i < 5; i++)
		paths[i] = stream->readSint32LE();

	volume = stream->readSint32LE();
}

void ActionArea::saveLoadWithSerializer(Common::Serializer &s) {
	s.syncBytes((byte *)&name, sizeof(name));
	s.syncAsSint32LE(id);
	s.syncAsSint32LE(field01);
	s.syncAsSint32LE(field02);
	s.syncAsSint32LE(field_40);
	s.syncAsSint32LE(field_44);
	s.syncAsSint32LE(flags);
	s.syncAsSint32LE(scriptIndex);
	s.syncAsSint32LE(scriptIndex2);
	s.syncAsSint32LE(actionType);

	for (int32 i = 0; i < ARRAYSIZE(flagNums); i++)
		s.syncAsSint32LE(flagNums[i]);

	s.syncAsSint32LE(field_7C);
	s.syncAsUint32LE(polygonIndex);
	s.syncAsSint32LE(soundResourceIdFrame);
	s.syncAsSint32LE(field_88);
	s.syncAsSint32LE(soundResourceId);
	s.syncAsSint32LE(field_90);
	s.syncAsSint32LE(paletteResourceId);

	for (int32 i = 0; i < 5; i++)
		s.syncAsSint32LE(paths[i]);

	s.syncAsSint32LE(volume);
}

//////////////////////////////////////////////////////////////////////////
// ScriptManager
//////////////////////////////////////////////////////////////////////////
ScriptManager::ScriptManager(AsylumEngine *engine) : _vm(engine) {
	// Build list of opcodes
	ADD_OPCODE(Return);
	ADD_OPCODE(SetGameFlag);
	ADD_OPCODE(ClearGameFlag);
	ADD_OPCODE(ToggleGameFlag);
	ADD_OPCODE(JumpIfGameFlag);
	ADD_OPCODE(HideCursor);
	ADD_OPCODE(ShowCursor);
	ADD_OPCODE(PlayAnimation);
	ADD_OPCODE(MoveScenePosition);
	ADD_OPCODE(HideActor);
	ADD_OPCODE(ShowActor);
	ADD_OPCODE(SetActorPosition);
	ADD_OPCODE(SetSceneMotionStatus);
	ADD_OPCODE(DisableActor);
	ADD_OPCODE(EnableActor);
	ADD_OPCODE(EnableObjects);
	ADD_OPCODE(Return);
	ADD_OPCODE(RemoveObject);
	ADD_OPCODE(JumpActorSpeech);
	ADD_OPCODE(JumpAndSetDirection);
	ADD_OPCODE(JumpIfActorCoordinates);
	ADD_OPCODE(Nop);
	ADD_OPCODE(ResetAnimation);
	ADD_OPCODE(DisableObject);
	ADD_OPCODE(JumpIfSoundPlayingAndPlaySound);
	ADD_OPCODE(JumpIfActionFind);
	ADD_OPCODE(SetActionFind);
	ADD_OPCODE(ClearActionFind);
	ADD_OPCODE(JumpIfActionGrab);
	ADD_OPCODE(SetActionGrab);
	ADD_OPCODE(ClearActionGrab);
	ADD_OPCODE(JumpIfActionTalk);
	ADD_OPCODE(SetActionTalk);
	ADD_OPCODE(ClearActionTalk);
	ADD_OPCODE(AddToInventory);
	ADD_OPCODE(RemoveFromInventory);
	ADD_OPCODE(JumpIfInventoryOmits);
	ADD_OPCODE(RunEncounter);
	ADD_OPCODE(JumpIfAction16);
	ADD_OPCODE(SetAction16);
	ADD_OPCODE(ClearAction16);
	ADD_OPCODE(SelectInventoryItem);
	ADD_OPCODE(JumpIfInventoryItemNotSelected);
	ADD_OPCODE(ChangeScene);
	ADD_OPCODE(Interact);
	ADD_OPCODE(PlayMovie);
	ADD_OPCODE(StopAllObjectsSounds);
	ADD_OPCODE(StopProcessing);
	ADD_OPCODE(ResumeProcessing);
	ADD_OPCODE(ResetSceneRect);
	ADD_OPCODE(ChangeMusicById);
	ADD_OPCODE(StopMusic);
	ADD_OPCODE(IncrementParam1);
	ADD_OPCODE(SetVolume);
	ADD_OPCODE(Jump);
	ADD_OPCODE(RunPuzzle);
	ADD_OPCODE(JumpIfAction8);
	ADD_OPCODE(SetAction8);
	ADD_OPCODE(ClearAction8);
	ADD_OPCODE(CreatePalette);
	ADD_OPCODE(IncrementParam2);
	ADD_OPCODE(WaitUntilFramePlayed);
	ADD_OPCODE(UpdateWideScreen);
	ADD_OPCODE(JumpIfActor);
	ADD_OPCODE(PlaySpeechScene);
	ADD_OPCODE(PlaySpeech);
	ADD_OPCODE(PlaySpeechScene2);
	ADD_OPCODE(MoveScenePositionFromActor);
	ADD_OPCODE(PaletteFade);
	ADD_OPCODE(QueuePaletteFade);
	ADD_OPCODE(PlaySoundUpdateObject);
	ADD_OPCODE(ActorFaceTarget);
	ADD_OPCODE(HideMatteBars);
	ADD_OPCODE(ShowMatteBars);
	ADD_OPCODE(JumpIfSoundPlaying);
	ADD_OPCODE(ChangePlayer);
	ADD_OPCODE(ChangeActorStatus);
	ADD_OPCODE(StopSound);
	ADD_OPCODE(JumpRandom);
	ADD_OPCODE(ClearScreen);
	ADD_OPCODE(Quit);
	ADD_OPCODE(JumpObjectFrame);
	ADD_OPCODE(DeleteGraphics);
	ADD_OPCODE(SetActorField944);
	ADD_OPCODE(SetScriptField1BB0);
	ADD_OPCODE(OnScriptField1BB0);
	ADD_OPCODE(WalkToActor);
	ADD_OPCODE(SetResourcePalette);
	ADD_OPCODE(SetObjectFrameIndexAndFlags);
	ADD_OPCODE(SetObjectFlags);
	ADD_OPCODE(SetActorActionIndex2);
	ADD_OPCODE(UpdateTransparency);
	ADD_OPCODE(QueueScript);
	ADD_OPCODE(ProcessActor);
	ADD_OPCODE(ClearActorFields);
	ADD_OPCODE(SetObjectLastFrameIndex);
	ADD_OPCODE(SetActionAreaFlags);
	ADD_OPCODE(MorphActor);
	ADD_OPCODE(ShowMenu);
	ADD_OPCODE(UpdateGlobalFlags);

	reset();
}

ScriptManager::~ScriptManager() {
	for (int i = 0; i < (int)_opcodes.size(); i++)
		delete _opcodes[i];

	_scripts.clear();
	_queue.reset();
}

void ScriptManager::load(Common::SeekableReadStream *stream) {
	stream->readSint32LE();  // size
	int32 numEntries = stream->readSint32LE();

	for (int32 a = 0; a < numEntries; a++) {
		Script script;

		for (int32 c = 0; c < MAX_ACTION_COMMANDS; c++) {
			ScriptEntry command;

			command.numLines = stream->readSint32LE();
			command.opcode   = (OpcodeType)stream->readSint32LE();
			command.param1   = stream->readSint32LE();
			command.param2   = stream->readSint32LE();
			command.param3   = stream->readSint32LE();
			command.param4   = stream->readSint32LE();
			command.param5   = stream->readSint32LE();
			command.param6   = stream->readSint32LE();
			command.param7   = stream->readSint32LE();
			command.param8   = stream->readSint32LE();
			command.param9   = stream->readSint32LE();

			script.commands[c] = command;

#ifdef DEBUG
			// Output encounter info
			if (command.opcode == kOpcodeRunEncounter)
				debugC(kDebugLevelEncounter, "%d, %d, %d, %d", command.param1, command.param2, command.param3, command.param4);
#endif
		}

		if (_vm->checkGameVersion("Demo")) {
			stream->seek(2 * 4, SEEK_CUR);
		} else {
			script.field_1BAC = stream->readSint32LE();
			script.field_1BB0 = stream->readSint32LE();
			script.counter    = stream->readSint32LE();
		}

		_scripts.push_back(script);
	}

	// Patch for Chapter 2 Lockout bug
	if (_vm->checkGameVersion("Unpatched") && getWorld()->chapter == kChapter2) {
		_scripts[ 3].commands[ 2].param1 = 1506;
		_scripts[34].commands[13].param1 =  453;
		_scripts[43].commands[ 9].param1 =  455;
	}

	// Patch for Demo lockup bug
	if (_vm->checkGameVersion("Demo")) {
		_scripts[1].commands[6].param2 = 151;
		_scripts[1].commands[6].param3 = 332;
	}
}

void ScriptManager::saveLoadWithSerializer(Common::Serializer &s) {
	for (uint i = 0; i < _scripts.size(); i++)
		_scripts[i].saveLoadWithSerializer(s);
}

// Save the script queue (in the original, it is part of the shared data)
void ScriptManager::saveQueue(Common::Serializer &s) {
	_queue.saveLoadWithSerializer(s);
}

void ScriptManager::resetAll() {
	_scripts.clear();
	resetQueue();
	reset();
}

void ScriptManager::reset(uint32 count) {
	// Create a set of empty scripts
	_scripts.clear();
	for (uint32 i = 0; i < count; i++) {
		Script script;
		_scripts.push_back(script);
	}

	_done = false;
	_exit = false;
	_processNextEntry = false;

	_lastProcessedCmd = nullptr;
	_currentScript = nullptr;
	_currentQueueEntry = nullptr;
}

void ScriptManager::resetQueue() {
	_queue.reset();
}

void ScriptManager::queueScript(int32 scriptIndex, ActorIndex actorIndex) {
	// When the skipProcessing flag is set, do not queue any more scripts
	if (getSharedData()->getFlag(kFlagSkipScriptProcessing))
		return;

	// Look for a empty queue slot
	uint32 index;
	for (index = 1; index < ARRAYSIZE(_queue.entries); index++) {
		if (_queue.entries[index].scriptIndex == -1)
			break;
	}

	// No empty queue slot, return
	if (index == 0 || index == ARRAYSIZE(_queue.entries))
		return;

	// Setup script and queue
	_scripts[scriptIndex].counter = 0;
	_queue.entries[index].prev = 0;
	_queue.entries[index].next = 0;

	if (_queue.first) {
		_queue.entries[_queue.last].next = index;
		_queue.entries[index].prev = _queue.last;
	} else {
		_queue.first = index;
	}

	_queue.last = index;

	_queue.entries[index].scriptIndex = scriptIndex;
	_queue.entries[index].currentLine = 0;
	_queue.entries[index].actorIndex = actorIndex;
}

bool ScriptManager::isInQueue(int32 scriptIndex) const {
	for (int32 i = 0; i < ARRAYSIZE(_queue.entries); i++) {
		if (_queue.entries[i].scriptIndex == scriptIndex)
			return true;
	}

	return false;
}

void ScriptManager::removeFromQueue(uint32 entryIndex) {
	if (_queue.first == _queue.last) {
		_queue.last = 0;
		_queue.first = 0;
		_queue.entries[entryIndex].scriptIndex = -1;
	} else if (_queue.first == entryIndex) {
		_queue.first = _queue.entries[entryIndex].next;
		_queue.entries[_queue.first].prev = 0;
		_queue.entries[entryIndex].scriptIndex = -1;
	} else if (_queue.last == entryIndex) {
		_queue.last = _queue.entries[entryIndex].prev;
		_queue.entries[_queue.last].next = 0;
		_queue.entries[entryIndex].scriptIndex = -1;
	} else {
		_queue.entries[_queue.entries[entryIndex].prev].next = _queue.entries[entryIndex].next;
		_queue.entries[_queue.entries[entryIndex].next].prev = _queue.entries[entryIndex].prev;
		_queue.entries[entryIndex].scriptIndex = -1;
	}
}

bool ScriptManager::process() {
	_exit = false;

	_vm->setGameFlag(kGameFlagScriptProcessing);

	// Setup queue entry
	if (_queue.first) {
		uint32 entryIndex  = _queue.first;
		uint32 nextIndex   = _queue.entries[entryIndex].next;
		int32  scriptIndex = _queue.entries[entryIndex].scriptIndex;

		if (scriptIndex != -1) {

			// Setup script
			for (;;) {
				_processNextEntry = false;
				_done             = false;
				_currentScript    = &_scripts[scriptIndex];

				// Run script
				for (;;) {
					ScriptEntry *cmd = nullptr;
					uint32 cmdIndex = 0;

					if (_processNextEntry)
						goto label_processNextEntry;

					// Get the script command
					cmdIndex = (uint32)_queue.entries[entryIndex].currentLine;
					if (cmdIndex >= MAX_ACTION_COMMANDS)
						error("[ScriptManager::process] Invalid command index (was: %d, max: %d)", cmdIndex, MAX_ACTION_COMMANDS);

					cmd = &_currentScript->commands[cmdIndex];

					// Check script opcode
					if (cmd->opcode >= (int32)_opcodes.size())
						error("[ScriptManager::process] Invalid opcode index (was: %d, max: %d)", cmd->opcode, _opcodes.size() - 1);

					if (_lastProcessedCmd != cmd)
						debugC(kDebugLevelCommands, "[Script idx: %d] %2d: %s (%d, %d, %d, %d, %d, %d, %d, %d, %d)",
							scriptIndex, cmdIndex, _opcodes[cmd->opcode]->name,
							cmd->param1, cmd->param2, cmd->param3, cmd->param4, cmd->param5,
							cmd->param6, cmd->param7, cmd->param8, cmd->param9);

					_lastProcessedCmd = cmd; // DEBUGGING

					// Execute opcode
					_currentQueueEntry = &_queue.entries[entryIndex];
					(*_opcodes[cmd->opcode]->func)(cmd);

					// When we are asked to exit, return directly without setting the processing flag
					if (_exit)
						return true;

					if (!_processNextEntry)
						++_queue.entries[entryIndex].currentLine;

					if (_done)
						removeFromQueue(entryIndex);

					if (!_done)
						continue;

label_processNextEntry:
					break;
				}

				entryIndex = nextIndex;

				if (!nextIndex)
					goto label_exit_processing;

				scriptIndex = _queue.entries[nextIndex].scriptIndex;
				nextIndex = _queue.entries[nextIndex].next;

				if (scriptIndex == -1)
						goto label_exit_processing;
			}
		}
	}

label_exit_processing:
	_vm->clearGameFlag(kGameFlagScriptProcessing);

	return false;
}

//////////////////////////////////////////////////////////////////////////
// Opcode Functions
//////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
// Opcode 0x00
IMPLEMENT_OPCODE(Return)
	_done          = true;
	_processNextEntry = false;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x01
IMPLEMENT_OPCODE(SetGameFlag)
	int32 flagNum = cmd->param1;

	if (flagNum >= 0)
		_vm->setGameFlag((GameFlag)flagNum);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x02
IMPLEMENT_OPCODE(ClearGameFlag)
	int32 flagNum = cmd->param1;

	if (flagNum >= 0)
		_vm->clearGameFlag((GameFlag)flagNum);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x03
IMPLEMENT_OPCODE(ToggleGameFlag)
	int32 flagNum = cmd->param1;

	if (flagNum >= 0)
		_vm->toggleGameFlag((GameFlag)flagNum);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x04
IMPLEMENT_OPCODE(JumpIfGameFlag)
	if (cmd->param1 < 0)
		return;

	bool doJump = (cmd->param2) ? _vm->isGameFlagNotSet((GameFlag)cmd->param1) : _vm->isGameFlagSet((GameFlag)cmd->param1);
	if (!doJump)
		return;

	setNextLine(cmd->param3);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x05
IMPLEMENT_OPCODE(HideCursor)
	getCursor()->hide();
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x06
IMPLEMENT_OPCODE(ShowCursor)
	getCursor()->show();

	getSharedData()->setFlag(kFlag1, false);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x07
IMPLEMENT_OPCODE(PlayAnimation)
	Object *object = getWorld()->getObjectById((ObjectId)cmd->param1);
	if (!object)
		error("[ScriptManager::opcodePlayAnimation] Cannot find specified object (id: %d)", cmd->param1);

	if (cmd->param2 == 2) {
		if (object->checkFlags()) {
			_processNextEntry = true;
		} else
			cmd->param2 = 1;

		return;
	}

	// Update flags
	if (cmd->param4) {
		object->flags &= ~kObjectFlag10E38;
		object->flags |= kObjectFlag20;
	} else if (cmd->param3) {
		object->flags &= ~kObjectFlag10E38;
		object->flags |= kObjectFlag10000;
	} else if (object->flags & kObjectFlag10000) {
		object->flags |= kObjectFlag8;
		object->flags &= ~kObjectFlag10000;
	} else if (!(object->flags & kObjectFlag10E38)) {
		object->flags |= kObjectFlag8;
	}

	object->setNextFrame(object->flags);

	if (object->getField688() == 1) {
		if (object->flags & kObjectFlag4) {
			getSharedData()->setGlobalPoint(Common::Point(object->x, object->y));
		} else {
			Common::Rect frameRect = GraphicResource::getFrameRect(_vm, object->getResourceId(), object->getFrameIndex());

			getSharedData()->setGlobalPoint(Common::Point(frameRect.left + (int16)Common::Rational(frameRect.width(), 2).toInt()  + object->x,
			                                              frameRect.top  + (int16)Common::Rational(frameRect.height(), 2).toInt() + object->y));
		}
	}

	if (cmd->param2) {
		cmd->param2 = 2;
		_processNextEntry = true;
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x08
IMPLEMENT_OPCODE(MoveScenePosition)
	if (cmd->param3 < 1) {
		getWorld()->xLeft = (int16)cmd->param1;
		getWorld()->yTop  = (int16)cmd->param2;
		getWorld()->motionStatus = 3;

	} else if (!cmd->param4) {
		getWorld()->motionStatus = 5;

		getScene()->updateSceneCoordinates(cmd->param1,
			                               cmd->param2,
		                                   cmd->param3);

	} else if (cmd->param5) {
		if (getWorld()->motionStatus == 2)
			_processNextEntry = true;
		else
			cmd->param5 = 0;
	} else {
		cmd->param5 = 1;
		getWorld()->motionStatus = 2;

		getScene()->updateSceneCoordinates(cmd->param1,
		                                   cmd->param2,
		                                   cmd->param3,
		                                   true);

		_processNextEntry = true;
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x09
IMPLEMENT_OPCODE(HideActor)
	Actor *actor = getScene()->getActor(cmd->param1);

	actor->hide();
	actor->updateReflectionData();
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x0A
IMPLEMENT_OPCODE(ShowActor)
	Actor *actor = getScene()->getActor(cmd->param1);

	actor->show();
	actor->updateReflectionData();
	actor->setLastScreenUpdate(_vm->getTick());
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x0B
IMPLEMENT_OPCODE(SetActorPosition)
	Actor *actor = getScene()->getActor(cmd->param1);

	actor->setPosition((int16)cmd->param2, (int16)cmd->param3, (ActorDirection)cmd->param4, (uint32)cmd->param5);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x0C
IMPLEMENT_OPCODE(SetSceneMotionStatus)
	getWorld()->motionStatus = cmd->param1;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x0D
IMPLEMENT_OPCODE(DisableActor)
	Actor *actor = getScene()->getActor(cmd->param1);

	actor->changeStatus(kActorStatusDisabled);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x0E
IMPLEMENT_OPCODE(EnableActor)
	Actor *actor = getScene()->getActor(cmd->param1);

	if (actor->getStatus() == kActorStatusDisabled)
		actor->enable();
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x0F
IMPLEMENT_OPCODE(EnableObjects)
	Object *object = getWorld()->getObjectById((ObjectId)cmd->param1);
	if (!object)
		error("[ScriptManager::opcodeEnableObjects] Cannot find specified object (id: %d)", cmd->param1);

	if (!_currentScript->counter && getWorld()->chapter != 13)
		_vm->sound()->playSound(cmd->param3 ? MAKE_RESOURCE(kResourcePackSound, 6) : MAKE_RESOURCE(kResourcePackSound, 1));

	if (_currentScript->counter >= (3 * cmd->param2 - 1)) {
		_currentScript->counter = 0;
		object->setTransparency(0);
		enableObject(cmd, kObjectTransparencyOpaque);
	} else {
		++_currentScript->counter;
		if (cmd->param3) {
			object->setTransparency(3 - _currentScript->counter / cmd->param2);
			enableObject(cmd, kObjectEnableType1);
		} else {
			object->setTransparency(_currentScript->counter / cmd->param2 + 1);
			enableObject(cmd, kObjectEnableType0);
		}

		_processNextEntry = true;
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x10 : Identical to opcode 0x00

//////////////////////////////////////////////////////////////////////////
// Opcode 0x11
IMPLEMENT_OPCODE(RemoveObject)
	if (!cmd->param1)
		return;

	Object *object = getWorld()->getObjectById((ObjectId)cmd->param1);
	if (!object)
		error("[ScriptManager::opcodeRemoveObject] Cannot find specified object (id: %d)", cmd->param1);

	object->disableAndRemoveFromQueue();
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x12
IMPLEMENT_OPCODE(JumpActorSpeech)
	Actor *actor = getScene()->getActor(cmd->param1);

	// Actor goes to position (param2, param3)
	if (actor->canReach(Common::Point((int16)cmd->param2, (int16)cmd->param3)))
		return;

	_currentQueueEntry->currentLine = cmd->param4;

	if (cmd->param5)
		getSpeech()->playIndexed(1);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x13
IMPLEMENT_OPCODE(JumpAndSetDirection)
	Actor *actor = getScene()->getActor(cmd->param1);
	ActorDirection newDirection = (ActorDirection)(cmd->param4 & 7);

	if (actor->getStatus() != kActorStatusWalkingTo && actor->getStatus() != kActorStatusWalkingTo2) {
		if (cmd->param5 != 2) {

			if (cmd->param2 == -1 || cmd->param3 == -1) {
				actor->changeDirection(newDirection);
			} else if ((actor->getPoint1()->x + actor->getPoint2()->x) == cmd->param2 && (actor->getPoint1()->y + actor->getPoint2()->y) == cmd->param3) {
				actor->changeDirection(newDirection);
			} else {
				actor->forceTo((int16)cmd->param2, (int16)cmd->param3, (bool)cmd->param6);

				if (cmd->param5 == 1) {
					cmd->param5 = 2;
					_processNextEntry = true;
				}
			}
		} else {
			cmd->param5 = 1;
			_processNextEntry = false;

			if ((actor->getPoint1()->x + actor->getPoint2()->x) == cmd->param2 && (actor->getPoint1()->y + actor->getPoint2()->y) == cmd->param3)
				actor->changeDirection(newDirection);
		}
	} else {
		if (cmd->param5 == 2)
			_processNextEntry = true;
	}
	
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x14
IMPLEMENT_OPCODE(JumpIfActorCoordinates)
	Actor *actor = getScene()->getActor(cmd->param1);

	if ((actor->getPoint1()->x + actor->getPoint2()->x) != cmd->param2 ||
		(actor->getPoint1()->y + actor->getPoint2()->y) != cmd->param3)
		_currentQueueEntry->currentLine = cmd->param4;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x15
IMPLEMENT_OPCODE(Nop)
	// Nothing to do
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x16
IMPLEMENT_OPCODE(ResetAnimation)
	Object *object = getWorld()->getObjectById((ObjectId)cmd->param1);
	if (!object)
		error("[ScriptManager::opcodeResetAnimation] Cannot find specified object (id: %d)", cmd->param1);

	if (object->flags & kObjectFlag10000)
		object->setFrameIndex(object->getFrameCount() - 1);
	else
		object->setFrameIndex(0);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x17
IMPLEMENT_OPCODE(DisableObject)
	Object *object = getWorld()->getObjectById((ObjectId)cmd->param1);
	if (!object)
		error("[ScriptManager::opcodeDisableObject] Cannot find specified object (id: %d)", cmd->param1);

	object->disable();
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x18
IMPLEMENT_OPCODE(JumpIfSoundPlayingAndPlaySound)
	ResourceId resource = (ResourceId)cmd->param1;

	if (cmd->param2 == 2) {
		if (getSound()->isPlaying(resource))
			_processNextEntry = true;
		else
			cmd->param2 = 1;
	} else if (!_vm->sound()->isPlaying(resource)) {
		int32 vol = getSound()->getAdjustedVolume(abs(Config.sfxVolume));
		getSound()->playSound(resource, (bool)cmd->param4, -((abs(cmd->param3) + vol) * (abs(cmd->param3) + vol)), 0);

		if (cmd->param2 == 1) {
			cmd->param2 = 2;
			_processNextEntry = true;
		}
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x19
IMPLEMENT_OPCODE(JumpIfActionFind)
	jumpIfActionFlag(cmd, kActionTypeFind);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x1A
IMPLEMENT_OPCODE(SetActionFind)
	setActionFlag(cmd, kActionTypeFind);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x1B
IMPLEMENT_OPCODE(ClearActionFind)
	clearActionFlag(cmd, kActionTypeFind);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x1C
IMPLEMENT_OPCODE(JumpIfActionGrab)
	jumpIfActionFlag(cmd, kActionTypeGrab);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x1D
IMPLEMENT_OPCODE(SetActionGrab)
	setActionFlag(cmd, kActionTypeGrab);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x1E
IMPLEMENT_OPCODE(ClearActionGrab)
	clearActionFlag(cmd, kActionTypeGrab);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x1F
IMPLEMENT_OPCODE(JumpIfActionTalk)
	jumpIfActionFlag(cmd, kActionTypeTalk);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x20
IMPLEMENT_OPCODE(SetActionTalk)
	setActionFlag(cmd, kActionTypeTalk);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x21
IMPLEMENT_OPCODE(ClearActionTalk)
	clearActionFlag(cmd, kActionTypeTalk);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x22
IMPLEMENT_OPCODE(AddToInventory)
	Actor *actor = getScene()->getActor(cmd->param3 ? cmd->param3 : _currentQueueEntry->actorIndex);

	actor->inventory.add(cmd->param1, cmd->param2);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x23
IMPLEMENT_OPCODE(RemoveFromInventory)
	Actor *actor = getScene()->getActor(cmd->param3 ? cmd->param3 : _currentQueueEntry->actorIndex);

	actor->inventory.remove(cmd->param1, cmd->param2);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x24
IMPLEMENT_OPCODE(JumpIfInventoryOmits)
	Actor *actor = getScene()->getActor(cmd->param4 ? cmd->param4 : _currentQueueEntry->actorIndex);

	if (!actor->inventory.contains(cmd->param1, cmd->param3))
		_currentQueueEntry->currentLine = cmd->param2;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x25
IMPLEMENT_OPCODE(RunEncounter)
	Encounter *encounter = _vm->encounter();

	getSharedData()->setFlag(kFlagEncounterDisablePlayerOnExit, (bool)cmd->param5);

	if (cmd->param6) {
		if (getSharedData()->getFlag(kFlagIsEncounterRunning))
			_processNextEntry = true;
		else
			cmd->param6 = 0;
	} else {
		encounter->run(cmd->param1, (ObjectId)cmd->param2, (ObjectId)cmd->param3, cmd->param4);

		cmd->param6 = 2;
		_processNextEntry = true;
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x26
IMPLEMENT_OPCODE(JumpIfAction16)
	jumpIfActionFlag(cmd, kActionType16);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x27
IMPLEMENT_OPCODE(SetAction16)
	setActionFlag(cmd, kActionType16);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x28
IMPLEMENT_OPCODE(ClearAction16)
	clearActionFlag(cmd, kActionType16);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x29
IMPLEMENT_OPCODE(SelectInventoryItem)
	Actor *actor = getScene()->getActor(cmd->param1);

	actor->inventory.selectItem(cmd->param2);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x2A
IMPLEMENT_OPCODE(JumpIfInventoryItemNotSelected)
	Actor *actor = getScene()->getActor(cmd->param1);

	if ((int32)actor->inventory.getSelectedItem() != cmd->param2)
		_currentQueueEntry->currentLine = cmd->param3;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x2B
IMPLEMENT_OPCODE(ChangeScene)
	if (_vm->isAltDemo()) {
		Engine::quitGame();
		_done = true;
		return;
	}

	uint32 tick = _vm->getTick();
	getScene()->getActor(0)->changeStatus(kActorStatusDisabled);
	resetQueue();

	// Fade screen to black
	getScreen()->paletteFade(0, 75, 8);
	getScreen()->clear();

	// Stop all sounds & music
	_vm->setTick(tick);
	getSound()->stopAll();
	getSound()->stopMusic();

	_vm->unlockAchievement(Common::String::format("ASYLUM_LEVEL_%d", getWorld()->chapter));

	// Switch the scene
	_vm->switchScene((ResourcePackId)(cmd->param1 + 4));

	_exit = true;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x2C
IMPLEMENT_OPCODE(Interact)
	Actor *player = getScene()->getActor();
	Actor *actor = getScene()->getActor(_currentQueueEntry->actorIndex);
	Common::Point playerPoint((int16)(player->getPoint1()->x + player->getPoint2()->x), (int16)(player->getPoint1()->y + player->getPoint2()->y));
	ActorDirection direction = (cmd->param2 == 8) ? player->getDirection() : (ActorDirection)cmd->param2;

	ActorDirection newDirection = (ActorDirection)((player->getDirection() + 4) % 8);

	if (cmd->param2 == 8)
		cmd->param2 = player->getDirection();

	if (cmd->param3 == 2) {
		switch (actor->getStatus()) {
		default:
			_processNextEntry = true;
			return;

		case kActorStatusEnabled:
		case kActorStatusEnabled2:
			return;

		case kActorStatusStoppedInteracting:
			actor->enable();
			break;

		case kActorStatusGettingHurt:
			// We want to continue processing and not go into the default case
			break;

		case kActorStatusStoppedHitting:
			actor->changeStatus(kActorStatusEnabled2);
		}

		cmd->param3 = 0;

		if (cmd->param1 == 2) {
			Common::Point point(playerPoint);

			if (player->canMove(&point, newDirection, 3, false)) {

				point.x += (int16)(3 * deltaPointsArray[newDirection][0]);
				point.y += (int16)(3 * deltaPointsArray[newDirection][1]);

				player->setPosition(point.x, point.y, actor->getDirection(), 0);
			}
		}

	} else if (cmd->param1 != 2 || player->canMove(&playerPoint, newDirection, 3, false)) {
		ResourceId id = kResourceNone;
		if (direction >= 5)
			id = actor->getResourcesId((uint32)(5 * cmd->param1 - (direction - 38)));
		else
			id = actor->getResourcesId((uint32)(5 * cmd->param1 + direction + 30));

		actor->setResourceId(id);
		actor->setFrameCount(GraphicResource::getFrameCount(_vm, id));
		actor->setFrameIndex(0);
		actor->setDirection(direction);
		actor->changeStatus(actor->getStatus() <= kActorStatus11 ? kActorStatusInteracting : kActorStatusHittingPumpkin);

		cmd->param3 = 2;
		_processNextEntry = true;
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x2D
IMPLEMENT_OPCODE(PlayMovie)
	if (_vm->checkGameVersion("Demo") && cmd->param1 == 4) {
		Engine::quitGame();
		_done = true;
		return;
	}

	if (getSharedData()->getMatteBarHeight() < 170) {
		_processNextEntry = true;

		if (!getSharedData()->getMatteBarHeight()) {
			getCursor()->hide();
			getScreen()->loadGrayPalette();
			getSharedData()->setMatteVar1(1);
			getSharedData()->setMatteBarHeight(1);
			getSharedData()->setMatteVar2(0);
			getSharedData()->setMattePlaySound(cmd->param3 == 0);
			getSharedData()->setMatteInitialized(cmd->param2 == 0);
			getSharedData()->movieIndex = (uint32)cmd->param1;
		}

		return;
	}

	bool check = false;
	ActionArea *area = getWorld()->actions[getScene()->getActor()->getActionIndex3()];
	if (area->paletteResourceId) {
		getScreen()->setPalette(area->paletteResourceId);
		getScreen()->setGammaLevel(area->paletteResourceId);
	} else {
		getScreen()->setPalette(getWorld()->currentPaletteId);
		getScreen()->setGammaLevel(getWorld()->currentPaletteId);
	}

	getSharedData()->setMatteBarHeight(0);
	_processNextEntry = false;

	if (!getSharedData()->getMattePlaySound() && _currentScript->commands[0].numLines != 0) {
		bool found = true;
		int index = 0;


		while (_currentScript->commands[index].opcode != 0x2B) { // ChangeScene
			++index;

			if (index >= _currentScript->commands[0].numLines) {
				found = false;
				break;
			}
		}

		if (found)
			check = true;
	}

	if (!check && getSharedData()->getMatteVar2() == 0 && getWorld()->musicCurrentResourceIndex != kMusicStopped)
		_vm->sound()->playMusic(MAKE_RESOURCE(kResourcePackMusic, getWorld()->musicCurrentResourceIndex));

	getCursor()->show();
	getSharedData()->setMatteVar2(0);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x2E
IMPLEMENT_OPCODE(StopAllObjectsSounds)
	Object *object = getWorld()->getObjectById((ObjectId)cmd->param1);

	object->stopAllSounds();
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x2F
IMPLEMENT_OPCODE(StopProcessing)
	getSharedData()->setFlag(kFlagSkipScriptProcessing, true);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x30
IMPLEMENT_OPCODE(ResumeProcessing)
	getSharedData()->setFlag(kFlagSkipScriptProcessing, false);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x31
IMPLEMENT_OPCODE(ResetSceneRect)
	getWorld()->sceneRectIdx = (uint8)LOBYTE(cmd->param1);
	getScreen()->paletteFade(0, 25, 10);
	getSharedData()->setFlag(kFlagScene1, true);

	getWorld()->xLeft = getWorld()->sceneRects[getWorld()->sceneRectIdx].left;
	getWorld()->yTop  = getWorld()->sceneRects[getWorld()->sceneRectIdx].top;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x32
IMPLEMENT_OPCODE(ChangeMusicById)
	_vm->sound()->changeMusic((ResourceId)cmd->param1, cmd->param2 ? 2 : 1);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x33
IMPLEMENT_OPCODE(StopMusic)
	_vm->sound()->changeMusic(kMusicStopped, 0);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x34
IMPLEMENT_OPCODE(IncrementParam1)
	if (cmd->param1 >= 2) {
		cmd->param1 = 0;
	} else {
		cmd->param1++;
		_processNextEntry = true;
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x35
IMPLEMENT_OPCODE(SetVolume)
	AmbientSoundItem item = getWorld()->ambientSounds[cmd->param1];
	int var = cmd->param2 + item.delta;

	int32 volume = -((Config.sfxVolume + var) * (Config.ambientVolume + var));

	if (volume < 0) {
		if (volume < -10000)
			volume = -10000;
	} else {
		volume = 0;
	}

	_vm->sound()->setVolume(item.resourceId, volume);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x36
IMPLEMENT_OPCODE(Jump)
	_currentQueueEntry->currentLine = cmd->param1 - 1;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x37
IMPLEMENT_OPCODE(RunPuzzle)
	getScreen()->clear();
	getScreen()->clearGraphicsInQueue();

	_vm->switchEventHandler(getPuzzles()->getPuzzle((uint32)cmd->param1));

	_currentQueueEntry->currentLine++;

	_exit = true;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x38
IMPLEMENT_OPCODE(JumpIfAction8)
	jumpIfActionFlag(cmd, kActionType8);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x39
IMPLEMENT_OPCODE(SetAction8)
	setActionFlag(cmd, kActionType8);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x3A
IMPLEMENT_OPCODE(ClearAction8)
	clearActionFlag(cmd, kActionType8);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x3B
IMPLEMENT_OPCODE(CreatePalette)
	if (!cmd->param2) {
		getScreen()->loadGrayPalette();
		cmd->param2 = 1;
	}

	if (cmd->param1 >= 22) {
		getScreen()->clear();

		cmd->param1 = 0;
		cmd->param2 = 0;
		_currentQueueEntry->currentLine++;

		getScreen()->clearGraphicsInQueue();

		_exit = true;
		return;
	}

	getScreen()->updatePalette(cmd->param1);

	_processNextEntry = true;
	++cmd->param1;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x3C
IMPLEMENT_OPCODE(IncrementParam2)
	if (cmd->param1) {
		if (cmd->param2 >= cmd->param1) {
			cmd->param2 = 0;
		} else {
			++cmd->param2;
			_processNextEntry = true;
		}
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x3D
IMPLEMENT_OPCODE(WaitUntilFramePlayed)
	Object *object = getWorld()->getObjectById((ObjectId)cmd->param1);

	int32 frameNum = cmd->param2;
	if (frameNum == -1)
		frameNum = object->getFrameCount() - 1;

	if ((int32)object->getFrameIndex() != frameNum)
		_processNextEntry = true;
END_OPCODE


//////////////////////////////////////////////////////////////////////////
// Opcode 0x3E
IMPLEMENT_OPCODE(UpdateWideScreen)
	int barSize = cmd->param1;

	if (barSize >= 22) {
		cmd->param1 = 0;
		_processNextEntry = false;

		getSharedData()->setMatteBarHeight(0);
	} else {
		getScreen()->drawWideScreenBars((int16)(4 * barSize));

		_processNextEntry = true;
		++cmd->param1;
	}
END_OPCODE


//////////////////////////////////////////////////////////////////////////
// Opcode 0x3F
IMPLEMENT_OPCODE(JumpIfActor)
	ActorIndex index = (cmd->param1 == kActorInvalid) ? getSharedData()->getPlayerIndex() : cmd->param1;

	if (_currentQueueEntry->actorIndex != index)
		_currentQueueEntry->currentLine = cmd->param2 - 1;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x40
IMPLEMENT_OPCODE(PlaySpeechScene)
	if (cmd->param1 < 0 || cmd->param1 >= 5)
		return;

	if (cmd->param4 != 2) {
		cmd->param5 = (int32)getSpeech()->playIndexed((ResourceId)cmd->param1);

		if (cmd->param2) {
			_vm->setGameFlag(kGameFlagScriptProcessing);
			cmd->param4 = 2;
			if (cmd->param6) {
				getSharedData()->setFlag(kFlag1, true);
				getSharedData()->setFlag(kFlag2, true);
			}
			_processNextEntry = true;
		}

		if (cmd->param3 && !cmd->param6)
			_vm->setGameFlag(kGameFlag219);

		return;
	}

	if (_vm->sound()->isPlaying((ResourceId)cmd->param5)) {
		_processNextEntry = true;
		return;
	}

	_vm->clearGameFlag(kGameFlagScriptProcessing);
	cmd->param4 = 0;

	if (cmd->param3) {
		if (cmd->param6) {
			getSharedData()->setFlag(kFlag1, false);
			getSharedData()->setFlag(kFlag2, false);

			return;
		} else {
			_vm->clearGameFlag(kGameFlag219);
		}
	}

	if (!cmd->param6) {
		cmd->param6 = 1;
	} else {
		getSharedData()->setFlag(kFlag1, false);
		getSharedData()->setFlag(kFlag2, false);
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x41
IMPLEMENT_OPCODE(PlaySpeech)
	if (cmd->param1 < 0)
		return;

	if (cmd->param4 != 2) {
		if (cmd->param1 == 153 && getWorld()->chapter == kChapter2)
			_vm->unlockAchievement("ASYLUM_FIND_CHILDREN");

		cmd->param5 = (int32)getSpeech()->playPlayer((ResourceId)cmd->param1);

		if (cmd->param2) {
			_vm->setGameFlag(kGameFlagScriptProcessing);
			cmd->param4 = 2;
			if (cmd->param6) {
				getSharedData()->setFlag(kFlag1, true);
				getSharedData()->setFlag(kFlag2, true);
			}
			_processNextEntry = true;
		}

		if (cmd->param3 && !cmd->param6)
			_vm->setGameFlag(kGameFlag219);

		return;
	}

	if (_vm->sound()->isPlaying((ResourceId)cmd->param5)) {
		_processNextEntry = true;
		return;
	}

	_vm->clearGameFlag(kGameFlagScriptProcessing);
	cmd->param4 = 0;

	if (cmd->param3) {
		if (cmd->param6) {
			getSharedData()->setFlag(kFlag1, false);
			getSharedData()->setFlag(kFlag2, false);

			return;
		} else {
			_vm->clearGameFlag(kGameFlag219);
		}
	}

	if (!cmd->param6) {
		cmd->param6 = 1;
	} else {
		getSharedData()->setFlag(kFlag1, false);
		getSharedData()->setFlag(kFlag2, false);
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x42
IMPLEMENT_OPCODE(PlaySpeechScene2)
	if (cmd->param1 < 0 || cmd->param1 >= 20 || cmd->param2 < 0)
		return;

	if (cmd->param5 == 2) {
		if (getSound()->isPlaying((ResourceId)cmd->param6)) {
			_processNextEntry = true;
			return;
		}

		_vm->clearGameFlag(kGameFlagScriptProcessing);

		cmd->param5 = 0;

		if (!cmd->param4) {
			if (!cmd->param7) {
				cmd->param7 = 1;
				return;
			}
		}

		if (!cmd->param7) {
			_vm->clearGameFlag(kGameFlag219);
			cmd->param7 = 1;
			return;
		}

		getSharedData()->setFlag(kFlag1, false);
		getSharedData()->setFlag(kFlag2, false);
		return;
	}

	cmd->param6 = (int32)getSpeech()->playScene(cmd->param1, cmd->param2);

	if (cmd->param3) {
		_vm->setGameFlag(kGameFlagScriptProcessing);

		cmd->param5 = 2;

		if (cmd->param7) {
			getSharedData()->setFlag(kFlag1, true);
			getSharedData()->setFlag(kFlag2, true);
		}

		_processNextEntry = true;
	}

	if (cmd->param4 && !cmd->param7)
		_vm->setGameFlag(kGameFlag219);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x43
IMPLEMENT_OPCODE(MoveScenePositionFromActor)
	Actor *actor = getScene()->getActor(cmd->param1);

	if (!cmd->param3) {
		getWorld()->motionStatus = 5;

		getScene()->updateSceneCoordinates(actor->getPoint1()->x + Common::Rational(actor->getPoint2()->x, 2).toInt() - 320,
		                                   actor->getPoint1()->y + Common::Rational(actor->getPoint2()->y, 2).toInt() - 240,
		                                   cmd->param2);
	} else if (cmd->param6) {
		if (getWorld()->motionStatus == 2) {
			_processNextEntry = true;
		} else {
			cmd->param6 = 0;
			getWorld()->coordinates[0] = -1;
		}
	} else {
		cmd->param6 = 1;
		getWorld()->motionStatus = 2;

		if (getScene()->updateSceneCoordinates(actor->getPoint1()->x + Common::Rational(actor->getPoint2()->x, 2).toInt() - 320,
		                                       actor->getPoint1()->y + Common::Rational(actor->getPoint2()->y, 2).toInt() - 240,
		                                       cmd->param2,
		                                       true,
		                                       &cmd->param6))
			_processNextEntry = false;
		else
			_processNextEntry = true;
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x44
IMPLEMENT_OPCODE(PaletteFade)
	getScreen()->paletteFade(0, cmd->param1, cmd->param2);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x45
IMPLEMENT_OPCODE(QueuePaletteFade)
	getScreen()->queuePaletteFade(getWorld()->currentPaletteId, cmd->param1, cmd->param2);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x46
IMPLEMENT_OPCODE(PlaySoundUpdateObject)
	if (cmd->param6) {
		if (getSound()->isPlaying(getSpeech()->getSoundResourceId())) {
			_processNextEntry = true;
		} else {
			cmd->param6 = 0;
			if (cmd->param5) {
				getScene()->getActor(cmd->param5)->enable();
			} else if (cmd->param4 != cmd->param3 && cmd->param4) {
				getWorld()->getObjectById((ObjectId)cmd->param3)->disable();

				Object *object = getWorld()->getObjectById((ObjectId)cmd->param4);
				object->setNextFrame(object->flags);
			}

			_vm->clearGameFlag(kGameFlagScriptProcessing);

			getSpeech()->resetResourceIds();
		}
	} else {
		_vm->setGameFlag(kGameFlagScriptProcessing);
		getSpeech()->play(MAKE_RESOURCE(kResourcePackSpeech, 515 + cmd->param1), MAKE_RESOURCE(kResourcePackText, 1290 + cmd->param1));

		if (cmd->param2) {
			getScene()->getActor(cmd->param5)->changeStatus(kActorStatus8);
			cmd->param6 = 1;
			_processNextEntry = true;
		} else {
			if (cmd->param4 != cmd->param3) {
				if (cmd->param4)
					getWorld()->getObjectById((ObjectId)cmd->param4)->disable();

				if (cmd->param3) {
					Object *object = getWorld()->getObjectById((ObjectId)cmd->param3);
					object->setNextFrame(object->flags);
				}
			}

			cmd->param6 = 1;
			_processNextEntry = true;
		}
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x47
IMPLEMENT_OPCODE(ActorFaceTarget)
	getScene()->getActor(cmd->param1)->faceTarget((uint32)cmd->param2, (DirectionFrom)cmd->param3);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x48
IMPLEMENT_OPCODE(HideMatteBars)
	getSharedData()->setMatteVar1(0);
	getSharedData()->setMatteInitialized(true);

	if (getSharedData()->getMatteBarHeight() >= 170) {
		getSharedData()->setMatteBarHeight(0);
		_processNextEntry = false;
		getCursor()->show();
	} else {
		_processNextEntry = true;

		if (!getSharedData()->getMatteBarHeight()) {
			getCursor()->hide();
			getSharedData()->setMatteBarHeight(1);
		}
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x49
IMPLEMENT_OPCODE(ShowMatteBars)
	getSharedData()->setMatteVar1(0);
	getSharedData()->setMatteInitialized(true);
	getSharedData()->setMattePlaySound(true);

	if (getSharedData()->getMatteBarHeight() >= 170) {
		getSharedData()->setMatteBarHeight(0);
		_processNextEntry = false;
		getCursor()->show();
	} else {
		_processNextEntry = true;

		if (!getSharedData()->getMatteBarHeight()) {
			getCursor()->hide();
			getSharedData()->setMatteBarHeight(90);
		}
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x4A
IMPLEMENT_OPCODE(JumpIfSoundPlaying)
	if (cmd->param3 == 1) {
		if (_vm->sound()->isPlaying((ResourceId)cmd->param1)) {
			_currentQueueEntry->currentLine = cmd->param2;
		}
	} else if (!_vm->sound()->isPlaying((ResourceId)cmd->param1)) {
		_currentQueueEntry->currentLine = cmd->param2;
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x4B
IMPLEMENT_OPCODE(ChangePlayer)
	getScene()->changePlayer(cmd->param1);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x4C
IMPLEMENT_OPCODE(ChangeActorStatus)
	Actor *actor = getScene()->getActor(cmd->param1);

	if (cmd->param2) {
		if (actor->getStatus() < kActorStatus11)
			actor->setStatus(kActorStatusEnabled2);
	} else {
		actor->setStatus(kActorStatusEnabled);
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x4D
IMPLEMENT_OPCODE(StopSound)
	if (_vm->sound()->isPlaying((ResourceId)cmd->param1))
		_vm->sound()->stop((ResourceId)cmd->param1);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x4E
IMPLEMENT_OPCODE(JumpRandom)
	if (_vm->getRandom((uint32)cmd->param1) < (uint32)cmd->param2)
		return;

	setNextLine(cmd->param3);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x4F
IMPLEMENT_OPCODE(ClearScreen)
	getSharedData()->setFlag(kFlagSkipDrawScene, (bool)cmd->param1);

	if (cmd->param1)
		getScreen()->clear();
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x50
IMPLEMENT_OPCODE(Quit)
	getScreen()->clear();
	Engine::quitGame();

	// We need to exit the interpreter loop so we get back to the event loop
	// and get the quit message
	_exit = true;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x51
IMPLEMENT_OPCODE(JumpObjectFrame)
	Object *object = getWorld()->getObjectById((ObjectId)cmd->param1);

	if (cmd->param2 == -1)
		cmd->param2 = object->getFrameCount() - 1;

	if (cmd->param3) {
		if (object->getFrameIndex() == (uint32)cmd->param2)
			return;
	} else if (cmd->param4) {
		if (object->getFrameIndex() < (uint32)cmd->param2)
			return;
	} else if (cmd->param5) {
		if (object->getFrameIndex() > (uint32)cmd->param2)
			return;
	} else if (cmd->param6) {
		if (object->getFrameIndex() <= (uint32)cmd->param2)
			return;
	} else if (cmd->param7) {
		if (object->getFrameIndex() >= (uint32)cmd->param2)
			return;
	} else if (!cmd->param8 || object->getFrameIndex() != (uint32)cmd->param2) {
		return;
	}

	setNextLine(cmd->param9);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x52
IMPLEMENT_OPCODE(DeleteGraphics)
	for (uint i = 0; i < 55; i++)
		getScreen()->deleteGraphicFromQueue(getScene()->getActor((ActorIndex)(cmd->param1))->getResourcesId((uint32)cmd->param1));
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x53
IMPLEMENT_OPCODE(SetActorField944)
	Actor *actor = getScene()->getActor(cmd->param1);

	actor->setField944(cmd->param2);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x54
IMPLEMENT_OPCODE(SetScriptField1BB0)
	if (cmd->param2)
		_currentScript->field_1BB0 = _vm->getRandom((uint32)cmd->param1);
	else
		_currentScript->field_1BB0 = cmd->param1;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x55
IMPLEMENT_OPCODE(OnScriptField1BB0)

	if (cmd->param2) {
		if (_currentScript->field_1BB0 == cmd->param1)
			return;
	} else {

		if (cmd->param3) {
			if (_currentScript->field_1BB0 < cmd->param1)
				return;
		} else if (cmd->param4) {
			if (_currentScript->field_1BB0 > cmd->param1)
				return;
		} else if (!cmd->param5) {
			if (cmd->param6) {
				if (_currentScript->field_1BB0 >= cmd->param1)
					return;
			} else {
				if (!cmd->param7 || _currentScript->field_1BB0 != cmd->param1)
					return;
			}
		} else {
			if (_currentScript->field_1BB0 <= cmd->param1)
				return;
		}
	}

	setNextLine(cmd->param8);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x56
IMPLEMENT_OPCODE(WalkToActor)
	Actor *player = getScene()->getActor(), *actor = getScene()->getActor((ActorIndex)cmd->param1);

	if (cmd->param2 == 2) {
		if (player->getStatus() == kActorStatusWalkingTo || player->getStatus() == kActorStatusWalkingTo2) {
			_processNextEntry = true;

			return;
		}

		cmd->param2 = 1;
		_processNextEntry = false;

		if ((player->getPoint1()->x + player->getPoint2()->x == cmd->param6)
		 && (player->getPoint1()->y + player->getPoint2()->y == cmd->param7)) {
			player->faceTarget((uint32)cmd->param1, kDirectionFromActor);
			actor->changeDirection((ActorDirection)((player->getDirection() + 4) & 7));
		} else {
			_currentQueueEntry->currentLine = cmd->param3;
		}
	} else {
		Common::Point point;

		if (actor->getStatus() == kActorStatusWalkingTo || actor->getStatus() == kActorStatusWalkingTo2)
			return;

		if (actor->aNicePlaceToTalk(&point, &cmd->param4)) {
			player->forceTo(point.x, point.y, (bool)cmd->param4);
			cmd->param6 = point.x;
			cmd->param7 = point.y;

			if (cmd->param2 == 1) {
				cmd->param2 = 2;
				_processNextEntry = true;
			}
		} else {
			if (cmd->param4)
				getSpeech()->playIndexed(1);

			_currentQueueEntry->currentLine = cmd->param3;
		}
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x57
IMPLEMENT_OPCODE(SetResourcePalette)
	getWorld()->currentPaletteId = getWorld()->graphicResourceIds[cmd->param1];
	getScreen()->setPalette(getWorld()->currentPaletteId);
	getScreen()->setGammaLevel(getWorld()->currentPaletteId);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x58
IMPLEMENT_OPCODE(SetObjectFrameIndexAndFlags)
	Object *object = getWorld()->getObjectById((ObjectId)cmd->param1);

	if (cmd->param3)
		object->flags = 1 | object->flags;
	else
		object->flags &= ~kObjectFlagEnabled;

	object->setFrameIndex(cmd->param2);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x59
IMPLEMENT_OPCODE(SetObjectFlags)
	Object *object = getWorld()->getObjectById((ObjectId)cmd->param1);

	if (cmd->param2) {
		object->flags |= kObjectFlag40000;
	} else {
		object->flags &= ~kObjectFlag10E38;
	}

	if (cmd->param3 && (object->flags & kObjectFlag10E38))
		_processNextEntry = true;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x5A
IMPLEMENT_OPCODE(SetActorActionIndex2)
	getScene()->getActor(cmd->param1)->setActionIndex2(cmd->param2);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x5B
IMPLEMENT_OPCODE(UpdateTransparency)
	if (cmd->param2 >= 0 && cmd->param2 <= 3) {
		if (cmd->param1) {
			Object *object = getWorld()->getObjectById((ObjectId)cmd->param1);
			if (!object) {
				debugC(kDebugLevelObjects, "Object with id %d doesn't exist", cmd->param1);
				return;
			}

			if (!cmd->param2)
				object->setTransparency(0);
			else
				object->setTransparency(cmd->param2 + 3);
		} else {
			getScene()->getActor(cmd->param3)->setTransparency(cmd->param2);
		}
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x5C
IMPLEMENT_OPCODE(QueueScript)
	queueScript(getWorld()->getActionAreaById(cmd->param1)->scriptIndex, cmd->param2);
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x5D
IMPLEMENT_OPCODE(ProcessActor)
	Actor *actor = getScene()->getActor(cmd->param1);

	actor->setupReflectionData(cmd->param2, cmd->param3, (ActorDirection)cmd->param4, Common::Point((int16)cmd->param5, (int16)cmd->param6), (bool)cmd->param7, Common::Point((int16)cmd->param8, (int16)cmd->param9));
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x5E
IMPLEMENT_OPCODE(ClearActorFields)
	Actor *actor = getScene()->getActor(cmd->param1);

	// Clear fields starting from field_970
	actor->clearReflectionData();
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x5F
IMPLEMENT_OPCODE(SetObjectLastFrameIndex)
	Object *object = getWorld()->getObjectById((ObjectId)cmd->param1);

	if (object->getFrameIndex() == object->getFrameCount() - 1) {
		_processNextEntry = false;
		object->flags &= ~kObjectFlag10E38;
	} else {
		_processNextEntry = true;
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x60
IMPLEMENT_OPCODE(SetActionAreaFlags)
	ActionArea *area = getWorld()->getActionAreaById(cmd->param1);

	if (cmd->param2)
		area->flags |= 1;
	else
		area->flags &= ~1;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x61
IMPLEMENT_OPCODE(MorphActor)
	if (cmd->param2) {
		if (getWorld()->nextPlayer == kActorInvalid) {
			_processNextEntry = false;
			cmd->param2 = 0;
		} else {
			_processNextEntry = true;
		}
	} else {
		Actor::morphInto(_vm, cmd->param1);
		cmd->param2 = 1;
		_processNextEntry = true;
	}
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x62
IMPLEMENT_OPCODE(ShowMenu)
	if (!_vm->isGameFlagSet(kGameFlag3931)) {
		_vm->unlockAchievement("ASYLUM_LEVEL_13");
		_vm->setGameFlag(kGameFlag3931);
	}
	_vm->menu()->show();
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode 0x63
IMPLEMENT_OPCODE(UpdateGlobalFlags)
	if (!_vm->sound()->isPlaying(getSpeech()->getSoundResourceId())) {
		if (cmd->param1) {
			getSharedData()->setFlag(kFlag1, false);
			getSharedData()->setFlag(kFlag2, false);
		} else {
			cmd->param1 = 1;
		}

		return;
	}

	if (cmd->param1) {
		getSharedData()->setFlag(kFlag1, true);
		getSharedData()->setFlag(kFlag2, true);
	}
	_processNextEntry = true;
END_OPCODE

//////////////////////////////////////////////////////////////////////////
// Opcode Helper functions
//////////////////////////////////////////////////////////////////////////

void ScriptManager::enableObject(ScriptEntry *cmd, ObjectTransparency type) {
	if (!_currentScript)
		error("[ScriptManager::enableObject] No current script");

	int32 transparency = 0;

	// Setup transparency
	switch (type) {
	default:
		break;

	case kObjectEnableType0:
		transparency = 4 + _currentScript->counter / cmd->param2;
		break;

	case kObjectEnableType1:
		transparency = 6 - _currentScript->counter / cmd->param2;
		break;

	case kObjectTransparencyOpaque:
		transparency = 0;
		break;
	}

	// Update first set of objects
	int32 *param = &cmd->param4;
	for (int i = 0; i < 6; i++) {
		Object *object = getWorld()->getObjectById((ObjectId)*param);
		if (object != nullptr)
			object->setTransparency(transparency);

		++param;
	}

	// Update per-chapter objects
	switch (getWorld()->chapter) {
	default:
		break;

	case kChapter3:
		if (cmd->param1 != 810)
			break;

		getWorld()->getObjectById(kObjectTableRecordRoom)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectOrangeRecord)->setTransparency(transparency);
		break;

	case kChapter4:
		if (cmd->param1 != 1232)
			break;

		getWorld()->getObjectById(kObjectInfernoStatusQuo)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectJugglerWithPin)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectJuggler)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectClownStatusQuo)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectTrixieStatusQuo)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectSimonStatusQuo)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectBigTopBarrel)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectStandBehindJuggler)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectStrongmanLeft)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectStrongmanRight)->setTransparency(transparency);
		break;

	case kChapter6:
		if (cmd->param1 == 1998) {
			getWorld()->getObjectById(kObjectGlow4)->setTransparency(transparency);
			getWorld()->getObjectById(kObjectBugOnTable)->setTransparency(transparency);
			getWorld()->getObjectById(kObjectInsidePipeCyberPod)->setTransparency(transparency);
			getWorld()->getObjectById(kObjectDiscardedBugPincer)->setTransparency(transparency);
			getWorld()->getObjectById(kObjectLitLimbScanner)->setTransparency(transparency);
			getWorld()->getObjectById(kObjectCyberTable)->setTransparency(transparency);
		}

		if (cmd->param1 == 2003) {
			getWorld()->getObjectById(kObjectNPC066StatusQuo)->setTransparency(transparency);
			/* getWorld()->getObjectById(kObject2507)->setTransparency(transparency); // XXX no such object */
			getWorld()->getObjectById(kObjectBrokenPipe)->setTransparency(transparency);
			getWorld()->getObjectById(kObjectEmberPopsOut)->setTransparency(transparency);
			getWorld()->getObjectById(kObjectBugCarriesEmber)->setTransparency(transparency);
			getWorld()->getObjectById(kObjectFurnaceHole)->setTransparency(transparency);
			getWorld()->getObjectById(kObjectTopOfFurnace)->setTransparency(transparency);
			getWorld()->getObjectById(kObjectElderBugLimb)->setTransparency(transparency);
		}
		break;

	case kChapter7:
		if (cmd->param1 != 1273)
			break;

		getWorld()->getObjectById(kObjectHeadOnTable)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectOfficeWallNew)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectChalice)->setTransparency(transparency);
		break;

	case kChapter8:
		if (cmd->param1 != 1795)
			break;

		getWorld()->getObjectById(kObjectHook1B)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectHook2B)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectHook3B)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectHook4B)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectHook5B)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectHook6B)->setTransparency(transparency);
		//getWorld()->getObjectById(kObjectHook0Down)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectHook2Down)->setTransparency(transparency);
		//getWorld()->getObjectById(kObjectHook3Down)->setTransparency(transparency);
		getWorld()->getObjectById(kObjectHook4Down)->setTransparency(transparency);
		//getWorld()->getObjectById(kObjectHook5Down)->setTransparency(transparency);
		//getWorld()->getObjectById(kObjectHook6Down)->setTransparency(transparency);
		//getWorld()->getObjectById(kObject2230)->setTransparency(transparency);
		break;
	}
}

void ScriptManager::setActionFlag(ScriptEntry *cmd, ActionType flag) {
	switch (cmd->param2) {
	default:
		if (!getWorld()->getObjectById((ObjectId)cmd->param1))
			return;

		getWorld()->getObjectById((ObjectId)cmd->param1)->actionType |= flag;
		break;

	case 1:
		getWorld()->getActionAreaById(cmd->param1)->actionType |= flag;
		break;

	case 2:
		getWorld()->actors[cmd->param1]->actionType |= flag;
		break;
	}
}

void ScriptManager::clearActionFlag(ScriptEntry *cmd, ActionType flag) {
	switch (cmd->param2) {
	default:
		if (!getWorld()->getObjectById((ObjectId)cmd->param1))
			return;

		getWorld()->getObjectById((ObjectId)cmd->param1)->actionType &= ~flag;
		break;

	case 1:
		getWorld()->getActionAreaById(cmd->param1)->actionType &= ~flag;
		break;

	case 2:
		getWorld()->actors[cmd->param1]->actionType &= ~flag;
		break;
	}
}

void ScriptManager::jumpIfActionFlag(ScriptEntry *cmd, ActionType flag) {
	bool done = false;

	switch (cmd->param3) {
	default:
		done = (getWorld()->actors[cmd->param1]->actionType & flag) == 0;
		break;

	case 0:
		done = (getWorld()->getObjectById((ObjectId)cmd->param1)->actionType & flag) == 0;
		break;

	case 1:
		done = (getWorld()->getActionAreaById(cmd->param1)->actionType & flag) == 0;
		break;
	}

	if (!done)
		return;

	setNextLine(cmd->param2);
}

void ScriptManager::setNextLine(int32 line) {
	if (!_currentScript)
		error("[ScriptManager::setNextLine] No current script");

	if (!_currentQueueEntry)
		error("[ScriptManager::setNextLine] No current queue entry");

	OpcodeType opcode = _currentScript->commands[line].opcode;
	if (opcode == kOpcodeReturn1 || opcode == kOpcodeReturn) {
		_currentQueueEntry->currentLine = line;
	} else {
		_done = true;
	}
}

} // end of namespace Asylum
