https://github.com/scummvm/scummvm
Raw File
Tip revision: cb053b5577305f0d04828e6152a199ea0f216d03 authored by Eugene Sandulenko on 28 April 2014, 18:07:36 UTC
Merge pull request #429 from sunmax/branch-1-5-0
Tip revision: cb053b5
script.cpp
/* 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.
 *
 */

// Scripting module: Script resource handling functions
#include "saga/saga.h"

#include "saga/gfx.h"
#include "saga/console.h"

#include "saga/animation.h"
#include "saga/script.h"
#include "saga/interface.h"
#include "saga/itedata.h"
#include "saga/scene.h"
#include "saga/events.h"
#include "saga/actor.h"
#include "saga/objectmap.h"
#include "saga/isomap.h"
#include "saga/resource.h"

namespace Saga {

#define RID_SCENE1_VOICE_START 57
#define RID_SCENE1_VOICE_END 186

SAGA1Script::SAGA1Script(SagaEngine *vm) : Script(vm) {
	ResourceContext *resourceContext;
	ByteArray resourceData;
	int prevTell;
	uint ui;
	int j;
	ByteArray stringsData;

	//initialize member variables
	_abortEnabled = true;
	_skipSpeeches = false;
	_conversingThread = NULL;
	_firstObjectSet = false;
	_secondObjectNeeded = false;
	_pendingVerb = getVerbType(kVerbNone);
	_currentVerb = getVerbType(kVerbNone);
	_stickyVerb = getVerbType(kVerbWalkTo);
	_leftButtonVerb = getVerbType(kVerbNone);
	_rightButtonVerb = getVerbType(kVerbNone);
	_pointerObject = ID_NOTHING;

	_staticSize = 0;
	_commonBuffer.resize(COMMON_BUFFER_SIZE);

	debug(8, "Initializing scripting subsystem");
	// Load script resource file context
	_scriptContext = _vm->_resource->getContext(GAME_SCRIPTFILE);
	if (_scriptContext == NULL) {
		error("Script::Script() script context not found");
	}

	resourceContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
	if (resourceContext == NULL) {
		error("Script::Script() resource context not found");
	}

	uint32 scriptResourceId = 0;
	scriptResourceId = _vm->getResourceDescription()->moduleLUTResourceId;
	debug(3, "Loading module LUT from resource %i", scriptResourceId);
	_vm->_resource->loadResource(resourceContext, scriptResourceId, resourceData);

	// Create logical script LUT from resource
	if (resourceData.size() % 22 == 0) {			// ITE CD
		_modulesLUTEntryLen = 22;
	} else if (resourceData.size() % 16 == 0) {	// ITE disk, IHNM
		_modulesLUTEntryLen = 16;
	} else {
		error("Script::Script() Invalid script lookup table length (%i)", (int)resourceData.size());
	}

	// Calculate number of entries
	int modulesCount = resourceData.size() / _modulesLUTEntryLen;

	debug(3, "LUT has %i entries", modulesCount);

	// Allocate space for logical LUT
	_modules.resize(modulesCount);

	// Convert LUT resource to logical LUT
	ByteArrayReadStreamEndian scriptS(resourceData, resourceContext->isBigEndian());
	for (ui = 0; ui < _modules.size(); ui++) {

		prevTell = scriptS.pos();
		_modules[ui].scriptResourceId = scriptS.readUint16();
		_modules[ui].stringsResourceId = scriptS.readUint16();
		_modules[ui].voicesResourceId = scriptS.readUint16();

		// Skip the unused portion of the structure
		for (j = scriptS.pos(); j < prevTell + _modulesLUTEntryLen; j++) {
			if (scriptS.readByte() != 0)
				warning("Unused scriptLUT part isn't really unused for LUT %d (pos: %d)", ui, j);
		}
	}

	// TODO
	//
	// In ITE, the "main strings" resource contains both the verb strings
	// and the object names.
	//
	// In IHNM, the "main strings" contains the verb strings, but not the
	// object names. At least, I think that's the case.

	_vm->_resource->loadResource(resourceContext, _vm->getResourceDescription()->mainStringsResourceId, stringsData);

	_vm->loadStrings(_mainStrings, stringsData);

	setupScriptOpcodeList();

	// Setup script functions
	switch (_vm->getGameId()) {
		case GID_ITE:
			setupITEScriptFuncList();
			break;
#ifdef ENABLE_IHNM
		case GID_IHNM:
			setupIHNMScriptFuncList();
			break;
#endif
	}
}

SAGA1Script::~SAGA1Script() {
	debug(8, "Shutting down scripting subsystem.");
}

SAGA2Script::SAGA2Script(SagaEngine *vm) : Script(vm) {
	ByteArray resourceData;

	debug(8, "Initializing scripting subsystem");
	// Load script resource file context
	_scriptContext = _vm->_resource->getContext(GAME_SCRIPTFILE);
	if (_scriptContext == NULL) {
		error("Script::Script() script context not found");
	}

	// Script export segment (lookup table)
	uint32 saga2ExportSegId = MKTAG('_','E','X','P');
	int32 entryNum = _scriptContext->getEntryNum(saga2ExportSegId);
	if (entryNum < 0)
		error("Unable to locate the script's export segment");
	debug(3, "Loading module LUT from resource %i", entryNum);
	_vm->_resource->loadResource(_scriptContext, (uint32)entryNum, resourceData);

	_modulesLUTEntryLen = sizeof(uint32);

	// Calculate number of entries
	int modulesCount = resourceData.size() / _modulesLUTEntryLen + 1;

	debug(3, "LUT has %i entries", modulesCount);

	// Script data segment
	/*
	uint32 saga2DataSegId = MKTAG('_','_','D','A');
	entryNum = _scriptContext->getEntryNum(saga2DataSegId);
	if (entryNum < 0)
		error("Unable to locate the script's data segment");
	debug(3, "Loading module data from resource %i", entryNum);
	_vm->_resource->loadResource(_scriptContext, (uint32)entryNum, resourcePointer, resourceLength);
	*/

	// TODO
}

SAGA2Script::~SAGA2Script() {
	debug(8, "Shutting down scripting subsystem.");

	// TODO
}

// Initializes the scripting module.
// Loads script resource look-up table, initializes script data system
Script::Script(SagaEngine *vm) : _vm(vm) {

}

// Shut down script module gracefully; free all allocated module resources
Script::~Script() {

}

// Script opcodes
#define OPCODE(x) {&Script::x, #x}

void Script::setupScriptOpcodeList() {
	static const ScriptOpDescription SAGA1ScriptOpcodes[] = {
		OPCODE(opDummy),		// 00: Undefined
		// Internal operations
		OPCODE(opNextBlock),	// 01: Continue execution at next block
		OPCODE(opDup),			// 02: Duplicate 16-bit value on stack
		OPCODE(opDrop),			// 03: Drop 16-bit value on stack
		// Primary values
		OPCODE(opZero),			// 04: Push a zero on the stack
		OPCODE(opOne),			// 05: Push a one on the stack
		OPCODE(opConstInt),		// 06: Constant integer
		OPCODE(opDummy),		// 07: Constant ID reference (unused)
		OPCODE(opStrLit),		// 08: String literal
		OPCODE(opDummy),		// 09: Symbol address (unused)
		OPCODE(opDummy),		// 10: Symbol contents (unused)
		// References within this module
		OPCODE(opGetFlag),		// 11: Read flag bit
		OPCODE(opGetInt),		// 12: Read integer
		OPCODE(opDummy),		// 13: Read string (unused)
		OPCODE(opDummy),		// 14: Read id (unused)
		OPCODE(opPutFlag),		// 15: Write flag bit
		OPCODE(opPutInt),		// 16: Write integer
		OPCODE(opDummy),		// 17: Write string (unused)
		OPCODE(opDummy),		// 18: Write id (unused)
		// Void versions, which consume their arguments
		OPCODE(opPutFlagV),		// 19: Write flag bit
		OPCODE(opPutIntV),		// 20: Write integer
		OPCODE(opDummy),		// 21: Write string (unused)
		OPCODE(opDummy),		// 22: Write id (unused)
		// Function calling
		OPCODE(opCall),			// 23: Call function
		OPCODE(opCcall),		// 24: Call C function
		OPCODE(opCcallV),		// 25: Call C function ()
		OPCODE(opEnter),		// 26: Enter a function
		OPCODE(opReturn),		// 27: Return from a function
		OPCODE(opReturnV),		// 28: Return from a function ()
		// Branching
		OPCODE(opJmp),			// 29
		OPCODE(opJmpTrueV),		// 30: Test argument and consume it
		OPCODE(opJmpFalseV),	// 31: Test argument and consume it
		OPCODE(opJmpTrue),		// 32: Test argument but don't consume it
		OPCODE(opJmpFalse),		// 33: Test argument but don't consume it
		OPCODE(opJmpSwitch),	// 34: Switch (integer)
		OPCODE(opDummy),		// 35: Switch (string) (unused)
		OPCODE(opJmpRandom),	// 36: Random jump
		// Unary operators
		OPCODE(opNegate),		// 37
		OPCODE(opNot),			// 38
		OPCODE(opCompl),		// 39
		OPCODE(opIncV),			// 40: Increment, don't push
		OPCODE(opDecV),			// 41: Increment, don't push
		OPCODE(opPostInc),		// 42
		OPCODE(opPostDec),		// 43
		// Arithmetic
		OPCODE(opAdd),			// 44
		OPCODE(opSub),			// 45
		OPCODE(opMul),			// 46
		OPCODE(opDiv),			// 47
		OPCODE(opMod),			// 48
		// Conditional
		OPCODE(opDummy),		// 49: opConditional (unused)
		OPCODE(opDummy),		// 50: opComma (unused)
		// Comparison
		OPCODE(opEq),			// 51
		OPCODE(opNe),			// 52
		OPCODE(opGt),			// 53
		OPCODE(opLt),			// 54
		OPCODE(opGe),			// 55
		OPCODE(opLe),			// 56
		// String comparison
		OPCODE(opDummy),		// 57: opStrEq (unused)
		OPCODE(opDummy),		// 58: opStrNe (unused)
		OPCODE(opDummy),		// 59: opStrGt (unused)
		OPCODE(opDummy),		// 60: opStrLt (unused)
		OPCODE(opDummy),		// 61: opStrGe (unused)
		OPCODE(opDummy),		// 62: opStrLe (unused)
		// Shift
		OPCODE(opRsh),			// 63
		OPCODE(opLsh),			// 64
		// Bitwise
		OPCODE(opAnd),			// 65
		OPCODE(opOr),			// 66
		OPCODE(opXor),			// 67
		// Logical
		OPCODE(opLAnd),			// 68
		OPCODE(opLOr),			// 69
		OPCODE(opLXor),			// 70
		// String manipulation
		OPCODE(opDummy),		// 71: opStrCat, string concatenation (unused)
		OPCODE(opDummy),		// 72: opStrFormat, string formatting (unused)
		// Assignment
		OPCODE(opDummy),		// 73: assign (unused)
		OPCODE(opDummy),		// 74: += (unused)
		OPCODE(opDummy),		// 75: -= (unused)
		OPCODE(opDummy),		// 76: *= (unused)
		OPCODE(opDummy),		// 77: /= (unused)
		OPCODE(opDummy),		// 78: %= (unused)
		OPCODE(opDummy),		// 79: <<= (unused)
		OPCODE(opDummy),		// 80: >>= (unused)
		OPCODE(opDummy),		// 81: and (unused)
		OPCODE(opDummy),		// 82: or (unused)
		// Special
		OPCODE(opSpeak),		// 83
		OPCODE(opDialogBegin),	// 84
		OPCODE(opDialogEnd),	// 85
		OPCODE(opReply),		// 86
		OPCODE(opAnimate)		// 87
	};

#ifdef ENABLE_SAGA2
	static const ScriptOpDescription SAGA2ScriptOpcodes[] = {
		OPCODE(opDummy),		// 00: Undefined
		// Internal operations
		OPCODE(opNextBlock),	// 01: Continue execution at next block
		OPCODE(opDup),			// 02: Duplicate 16-bit value on stack
		OPCODE(opDrop),			// 03: Drop 16-bit value on stack
		// Primary values
		OPCODE(opZero),			// 04: Push a zero on the stack
		OPCODE(opOne),			// 05: Push a one on the stack
		OPCODE(opConstInt),		// 06: Constant integer
		OPCODE(opDummy),		// 07: Constant ID reference (unused)
		OPCODE(opStrLit),		// 08: String literal
		OPCODE(opDummy),		// 09: Symbol address (unused)
		OPCODE(opDummy),		// 10: Symbol contents (unused)
		OPCODE(opDummy),		// 11: Reference to "this" (unused)
		OPCODE(opDummy),		// 12: Dereference of an ID (unused)
		// References within this module
		OPCODE(opGetFlag),		// 13: Read flag bit
		OPCODE(opGetByte),		// 14: Read byte
		OPCODE(opGetInt),		// 15: Read integer
		OPCODE(opDummy),		// 16: Read string (unused)
		OPCODE(opDummy),		// 17: Read id (unused)
		OPCODE(opPutFlag),		// 18: Write flag bit
		OPCODE(opPutByte),		// 19: Write byte
		OPCODE(opPutInt),		// 20: Write integer
		OPCODE(opDummy),		// 21: Write string (unused)
		OPCODE(opDummy),		// 22: Write id (unused)
		OPCODE(opDummy),		// 23: Push effective address (unused)
		// Void versions, which consume their arguments
		OPCODE(opPutFlagV),		// 24: Write flag bit
		OPCODE(opPutByteV),		// 25: Write byte
		OPCODE(opPutIntV),		// 26: Write integer
		OPCODE(opDummy),		// 27: Write string (unused)
		OPCODE(opDummy),		// 28: Write id (unused)
		// Function calling
		OPCODE(opCallNear),		// 29: Call function in the same segment
		OPCODE(opCallFar),		// 30: Call function in other segment
		OPCODE(opCcall),		// 31: Call C function
		OPCODE(opCcallV),		// 32: Call C function ()
		OPCODE(opCallMember),	// 33: Call member function
		OPCODE(opCallMemberV),	// 34: Call member function ()
		OPCODE(opEnter),		// 35: Enter a function
		OPCODE(opReturn),		// 36: Return from a function
		OPCODE(opReturnV),		// 37: Return from a function ()
		// Branching
		OPCODE(opJmp),			// 38
		OPCODE(opJmpTrueV),		// 39: Test argument and consume it
		OPCODE(opJmpFalseV),	// 40: Test argument and consume it
		OPCODE(opJmpTrue),		// 41: Test argument but don't consume it
		OPCODE(opJmpFalse),		// 42: Test argument but don't consume it
		OPCODE(opJmpSwitch),	// 43: Switch (integer)
		OPCODE(opDummy),		// 44: Switch (string) (unused)
		OPCODE(opJmpRandom),	// 45: Random jump
		// Unary operators
		OPCODE(opNegate),		// 46
		OPCODE(opNot),			// 47
		OPCODE(opCompl),		// 48
		OPCODE(opIncV),			// 49: Increment, don't push
		OPCODE(opDecV),			// 50: Increment, don't push
		OPCODE(opPostInc),		// 51
		OPCODE(opPostDec),		// 52
		// Arithmetic
		OPCODE(opAdd),			// 53
		OPCODE(opSub),			// 54
		OPCODE(opMul),			// 55
		OPCODE(opDiv),			// 56
		OPCODE(opMod),			// 57
		// Conditional
		OPCODE(opDummy),		// 58: opConditional (unused)
		OPCODE(opDummy),		// 59: opComma (unused)
		// Comparison
		OPCODE(opEq),			// 60
		OPCODE(opNe),			// 61
		OPCODE(opGt),			// 62
		OPCODE(opLt),			// 63
		OPCODE(opGe),			// 64
		OPCODE(opLe),			// 65
		// String comparison
		OPCODE(opDummy),		// 66: opStrEq (unused)
		OPCODE(opDummy),		// 67: opStrNe (unused)
		OPCODE(opDummy),		// 68: opStrGt (unused)
		OPCODE(opDummy),		// 69: opStrLt (unused)
		OPCODE(opDummy),		// 70: opStrGe (unused)
		OPCODE(opDummy),		// 71: opStrLe (unused)
		// Shift
		OPCODE(opRsh),			// 72
		OPCODE(opLsh),			// 73
		// Bitwise
		OPCODE(opAnd),			// 74
		OPCODE(opOr),			// 75
		OPCODE(opXor),			// 76
		// Logical
		OPCODE(opLAnd),			// 77
		OPCODE(opLOr),			// 78
		OPCODE(opLXor),			// 79
		// String manipulation
		OPCODE(opDummy),		// 80: opStrCat, string concatenation (unused)
		OPCODE(opDummy),		// 81: opStrFormat, string formatting (unused)
		// Assignment
		OPCODE(opDummy),		// 82: assign (unused)
		OPCODE(opDummy),		// 83: += (unused)
		OPCODE(opDummy),		// 84: -= (unused)
		OPCODE(opDummy),		// 85: *= (unused)
		OPCODE(opDummy),		// 86: /= (unused)
		OPCODE(opDummy),		// 87: %= (unused)
		OPCODE(opDummy),		// 88: <<= (unused)
		OPCODE(opDummy),		// 89: >>= (unused)
		OPCODE(opDummy),		// 90: and (unused)
		OPCODE(opDummy),		// 91: or (unused)
		// Special
		OPCODE(opSpeak),		// 92
		OPCODE(opDialogBegin),	// 93
		OPCODE(opDialogEnd),	// 94
		OPCODE(opReply),		// 95
		OPCODE(opAnimate),		// 96
		OPCODE(opJmpSeedRandom),// 97: Seeded random jump
		OPCODE(opDummy)			// 98: Get seeded export number (unused)
	};
#endif

	if (!_vm->isSaga2()) {
		_scriptOpsList = SAGA1ScriptOpcodes;
#ifdef ENABLE_SAGA2
	} else {
		_scriptOpsList = SAGA2ScriptOpcodes;
#endif
	}
}


void Script::opDup(SCRIPTOP_PARAMS) {
	thread->push(thread->stackTop());
}

void Script::opDrop(SCRIPTOP_PARAMS) {
	thread->pop();
}

void Script::opZero(SCRIPTOP_PARAMS) {
	thread->push(0);
}

void Script::opOne(SCRIPTOP_PARAMS) {
	thread->push(1);
}

void Script::opConstInt(SCRIPTOP_PARAMS) {
	thread->push(scriptS->readSint16LE());
}

void Script::opStrLit(SCRIPTOP_PARAMS) {
	thread->push(scriptS->readSint16LE());
}

void Script::opGetFlag(SCRIPTOP_PARAMS) {
	byte *addr = thread->baseAddress(scriptS->readByte());
	int16 iparam1 = scriptS->readSint16LE();
	addr += (iparam1 >> 3);
	iparam1 = (1 << (iparam1 & 7));
	thread->push((*addr) & iparam1 ? 1 : 0);
}

void Script::opGetByte(SCRIPTOP_PARAMS) {
	// SAGA 2 opcode
	// TODO
	warning("opGetByte");
}

void Script::opGetInt(SCRIPTOP_PARAMS) {
	byte mode = scriptS->readByte();
	byte *addr = thread->baseAddress(mode);
	int16 iparam1 = scriptS->readSint16LE();
	addr += iparam1;
	thread->push(readUint16(addr, mode));
	debug(8, "0x%X", readUint16(addr, mode));
}

void Script::opPutFlag(SCRIPTOP_PARAMS) {
	byte *addr = thread->baseAddress(scriptS->readByte());
	int16 iparam1 = scriptS->readSint16LE();
	addr += (iparam1 >> 3);
	iparam1 = (1 << (iparam1 & 7));
	if (thread->stackTop()) {
		*addr |= iparam1;
	} else {
		*addr &= ~iparam1;
	}
}

void Script::opPutByte(SCRIPTOP_PARAMS) {
	// SAGA 2 opcode
	// TODO
	warning("opPutByte");
}

void Script::opPutInt(SCRIPTOP_PARAMS) {
	byte mode = scriptS->readByte();
	byte *addr = thread->baseAddress(mode);
	int16 iparam1 = scriptS->readSint16LE();
	addr += iparam1;
	writeUint16(addr, thread->stackTop(), mode);
}

void Script::opPutFlagV(SCRIPTOP_PARAMS) {
	byte *addr = thread->baseAddress(scriptS->readByte());
	int16 iparam1 = scriptS->readSint16LE();
	addr += (iparam1 >> 3);
	iparam1 = (1 << (iparam1 & 7));
	if (thread->pop()) {
		*addr |= iparam1;
	} else {
		*addr &= ~iparam1;
	}
}

void Script::opPutByteV(SCRIPTOP_PARAMS) {
	// SAGA 2 opcode
	// TODO
	warning("opPutByteV");
}

void Script::opPutIntV(SCRIPTOP_PARAMS) {
	byte mode = scriptS->readByte();
	byte *addr = thread->baseAddress(mode);
	int16 iparam1 = scriptS->readSint16LE();
	addr += iparam1;
	writeUint16(addr, thread->pop(), mode);
}

void Script::opCall(SCRIPTOP_PARAMS) {
	byte argumentsCount = scriptS->readByte();
	int16 iparam1 = scriptS->readByte();
	if (iparam1 != kAddressModule) {
		error("Script::runThread iparam1 != kAddressModule");
	}
	iparam1 = scriptS->readSint16LE();
	thread->push(argumentsCount);

	// NOTE: The original pushes the program
	// counter as a pointer here. But I don't think
	// we will have to do that.
	thread->push(scriptS->pos());
	// NOTE2: program counter is 32bit - so we should "emulate" it size - because kAddressStack relies on it
	thread->push(0);
	thread->_instructionOffset = iparam1;
}

void Script::opCallNear(SCRIPTOP_PARAMS) {
	// SAGA 2 opcode
	// TODO
	warning("opCallNear");
}

void Script::opCallFar(SCRIPTOP_PARAMS) {
	// SAGA 2 opcode
	// TODO
	warning("opCallFar");
}

void Script::opCcall(SCRIPTOP_PARAMS) {
	byte argumentsCount = scriptS->readByte();
	uint16 functionNumber = scriptS->readUint16LE();
	if (functionNumber >= ((_vm->getGameId() == GID_IHNM) ?
						   IHNM_SCRIPT_FUNCTION_MAX : ITE_SCRIPT_FUNCTION_MAX)) {
		error("Script::opCcall() Invalid script function number (%d)", functionNumber);
	}

	debug(2, "Calling #%d %s argCount=%i", functionNumber, _scriptFunctionsList[functionNumber].scriptFunctionName, argumentsCount);
	ScriptFunctionType scriptFunction = _scriptFunctionsList[functionNumber].scriptFunction;
	uint16 checkStackTopIndex = thread->_stackTopIndex + argumentsCount;
	(this->*scriptFunction)(thread, argumentsCount, stopParsing);
	if (stopParsing)
		return;

	if (scriptFunction == &Saga::Script::sfScriptGotoScene) {
		stopParsing = true; // cause abortAllThreads called and _this_ thread destroyed
		breakOut = true;
		return;
	}

#ifdef ENABLE_IHNM
	if (scriptFunction == &Saga::Script::sfVsetTrack) {
		stopParsing = true;
		breakOut = true;
		return;		// cause abortAllThreads called and _this_ thread destroyed
	}
#endif

	thread->_stackTopIndex = checkStackTopIndex;

	thread->push(thread->_returnValue);		// return value

	if (thread->_flags & kTFlagAsleep)
		breakOut = true;	// break out of loop!
}

void Script::opCallMember(SCRIPTOP_PARAMS) {
	// SAGA 2 opcode
	// TODO
	warning("opCallMember");
}

void Script::opCallMemberV(SCRIPTOP_PARAMS) {
	// SAGA 2 opcode
	// TODO
	warning("opCallMemberV");
}

void Script::opCcallV(SCRIPTOP_PARAMS) {
	byte argumentsCount = scriptS->readByte();
	uint16 functionNumber = scriptS->readUint16LE();
	if (functionNumber >= ((_vm->getGameId() == GID_IHNM) ?
						   IHNM_SCRIPT_FUNCTION_MAX : ITE_SCRIPT_FUNCTION_MAX)) {
		error("Script::opCcallV() Invalid script function number (%d)", functionNumber);
	}

	debug(2, "Calling #%d %s argCount=%i", functionNumber, _scriptFunctionsList[functionNumber].scriptFunctionName, argumentsCount);
	ScriptFunctionType scriptFunction = _scriptFunctionsList[functionNumber].scriptFunction;
	uint16 checkStackTopIndex = thread->_stackTopIndex + argumentsCount;
	(this->*scriptFunction)(thread, argumentsCount, stopParsing);
	if (stopParsing)
		return;

	if (scriptFunction == &Saga::Script::sfScriptGotoScene) {
		stopParsing = true;
		breakOut = true;
		return;		// cause abortAllThreads called and _this_ thread destroyed
	}

#ifdef ENABLE_IHNM
	if (scriptFunction == &Saga::Script::sfVsetTrack) {
		stopParsing = true;
		breakOut = true;
		return;		// cause abortAllThreads called and _this_ thread destroyed
	}
#endif

	thread->_stackTopIndex = checkStackTopIndex;

	if (thread->_flags & kTFlagAsleep)
		breakOut = true;	// break out of loop!
}

void Script::opEnter(SCRIPTOP_PARAMS) {
	thread->push(thread->_frameIndex);
	thread->_frameIndex = thread->_stackTopIndex;
	thread->_stackTopIndex -= (scriptS->readSint16LE() / 2);
}

void Script::opReturn(SCRIPTOP_PARAMS) {
	thread->_returnValue = thread->pop();		// return value

	thread->_stackTopIndex = thread->_frameIndex;
	thread->_frameIndex = thread->pop();
	if (thread->pushedSize() == 0) {
		thread->_flags |= kTFlagFinished;
		stopParsing = true;
		breakOut = true;
		return;
	} else {
		thread->pop(); //cause it 0
		thread->_instructionOffset = thread->pop();

		// Pop all the call parameters off the stack
		int16 iparam1 = thread->pop();
		while (iparam1--) {
			thread->pop();
		}

		thread->push(thread->_returnValue);
	}
}

void Script::opReturnV(SCRIPTOP_PARAMS) {
	thread->_stackTopIndex = thread->_frameIndex;
	thread->_frameIndex = thread->pop();
	if (thread->pushedSize() == 0) {
		thread->_flags |= kTFlagFinished;
		stopParsing = true;
		breakOut = true;
		return;
	} else {
		thread->pop(); //cause it 0
		thread->_instructionOffset = thread->pop();

		// Pop all the call parameters off the stack
		int16 iparam1 = thread->pop();
		while (iparam1--) {
			thread->pop();
		}
	}
}

void Script::opJmp(SCRIPTOP_PARAMS) {
	thread->_instructionOffset = scriptS->readUint16LE();
}

void Script::opJmpTrueV(SCRIPTOP_PARAMS) {
	uint16 jmpOffset1 = scriptS->readUint16LE();
	if (thread->pop())
		thread->_instructionOffset = jmpOffset1;
}

void Script::opJmpFalseV(SCRIPTOP_PARAMS) {
	uint16 jmpOffset1 = scriptS->readUint16LE();
	if (!thread->pop())
		thread->_instructionOffset = jmpOffset1;
}

void Script::opJmpTrue(SCRIPTOP_PARAMS) {
	uint16 jmpOffset1 = scriptS->readUint16LE();
	if (thread->stackTop())
		thread->_instructionOffset = jmpOffset1;
}

void Script::opJmpFalse(SCRIPTOP_PARAMS) {
	uint16 jmpOffset1 = scriptS->readUint16LE();
	if (!thread->stackTop())
		thread->_instructionOffset = jmpOffset1;
}

void Script::opJmpSwitch(SCRIPTOP_PARAMS) {
	int16 iparam1 = scriptS->readSint16LE();
	int16 iparam2 = thread->pop();
	int16 iparam3;

	while (iparam1--) {
		iparam3 = scriptS->readUint16LE();
		thread->_instructionOffset = scriptS->readUint16LE();
		if (iparam3 == iparam2)
			break;
	}

	if (iparam1 < 0)
		thread->_instructionOffset = scriptS->readUint16LE();
}

void Script::opJmpRandom(SCRIPTOP_PARAMS) {
	// Supposedly the number of possible branches.
	// The original interpreter ignores it.
	scriptS->readUint16LE();
	int16 iparam1 = scriptS->readSint16LE();
	iparam1 = _vm->_rnd.getRandomNumber(iparam1 - 1);
	int16 iparam2;

	while (1) {
		iparam2 = scriptS->readSint16LE();
		thread->_instructionOffset = scriptS->readUint16LE();

		iparam1 -= iparam2;
		if (iparam1 < 0)
			break;
	}
}

void Script::opNegate(SCRIPTOP_PARAMS) {
	thread->push(-thread->pop());
}

void Script::opNot(SCRIPTOP_PARAMS) {
	thread->push(!thread->pop());
}

void Script::opCompl(SCRIPTOP_PARAMS) {
	thread->push(~thread->pop());
}

void Script::opIncV(SCRIPTOP_PARAMS) {
	byte mode = scriptS->readByte();
	byte *addr = thread->baseAddress(mode);
	int16 iparam1 = scriptS->readSint16LE();
	addr += iparam1;
	iparam1 = readUint16(addr, mode);
	writeUint16(addr, iparam1 + 1, mode);
}

void Script::opDecV(SCRIPTOP_PARAMS) {
	byte mode = scriptS->readByte();
	byte *addr = thread->baseAddress(mode);
	int16 iparam1 = scriptS->readSint16LE();
	addr += iparam1;
	iparam1 = readUint16(addr, mode);
	writeUint16(addr, iparam1 - 1, mode);
}

void Script::opPostInc(SCRIPTOP_PARAMS) {
	byte mode = scriptS->readByte();
	byte *addr = thread->baseAddress(mode);
	int16 iparam1 = scriptS->readSint16LE();
	addr += iparam1;
	iparam1 = readUint16(addr, mode);
	thread->push(iparam1);
	writeUint16(addr, iparam1 + 1, mode);
}

void Script::opPostDec(SCRIPTOP_PARAMS) {
	byte mode = scriptS->readByte();
	byte *addr = thread->baseAddress(mode);
	int16 iparam1 = scriptS->readSint16LE();
	addr += iparam1;
	iparam1 = readUint16(addr, mode);
	thread->push(iparam1);
	writeUint16(addr, iparam1 - 1, mode);
}

void Script::opAdd(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push(iparam1 + iparam2);
}

void Script::opSub(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push(iparam1 - iparam2);
}

void Script::opMul(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push(iparam1 * iparam2);
}

void Script::opDiv(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push(iparam1 / iparam2);
}

void Script::opMod(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push(iparam1 % iparam2);
}

void Script::opEq(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push((iparam1 == iparam2) ? 1 : 0);
}

void Script::opNe(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push((iparam1 != iparam2) ? 1 : 0);
}

void Script::opGt(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push((iparam1 > iparam2) ? 1 : 0);
}

void Script::opLt(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push((iparam1 < iparam2) ? 1 : 0);
}

void Script::opGe(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push((iparam1 >= iparam2) ? 1 : 0);
}

void Script::opLe(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push((iparam1 <= iparam2) ? 1 : 0);
}

void Script::opRsh(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push(iparam1 >> iparam2);
}

void Script::opLsh(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push(iparam1 << iparam2);
}

void Script::opAnd(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push(iparam1 & iparam2);
}

void Script::opOr(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push(iparam1 | iparam2);
}

void Script::opXor(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push(iparam1 ^ iparam2);
}

void Script::opLAnd(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push((iparam1 && iparam2) ? 1 : 0);
}

void Script::opLOr(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push((iparam1 || iparam2) ? 1 : 0);
}

void Script::opLXor(SCRIPTOP_PARAMS) {
	int16 iparam2 = thread->pop();
	int16 iparam1 = thread->pop();
	thread->push(((iparam1 && !iparam2) || (!iparam1 && iparam2)) ? 1 : 0);
}

void Script::opSpeak(SCRIPTOP_PARAMS) {
	if (_vm->_actor->isSpeaking()) {
		thread->wait(kWaitTypeSpeech);
		stopParsing = true;
		breakOut = false;
		return;
	}

#ifdef ENABLE_IHNM
	// WORKAROUND for script bug #3358007 in IHNM. When the zeppelin is landing
	// and the player attempts to exit from the right door in room 13, the game
	// scripts change to scene 5, but do not clear the cutaway that appears
	// before Gorrister's speech starts, resulting in a deadlock. We do this
	// manually here.
	if (_vm->getGameId() == GID_IHNM && _vm->_scene->currentChapterNumber() == 1 && 
		_vm->_scene->currentSceneNumber() == 5 && _vm->_anim->hasCutaway()) {
		_vm->_anim->returnFromCutaway();
	}
#endif

	int stringsCount = scriptS->readByte();
	uint16 actorId = scriptS->readUint16LE();
	uint16 speechFlags = scriptS->readByte();
	int sampleResourceId = -1;
	int16 first;
	const char *strings[ACTOR_SPEECH_STRING_MAX];

	scriptS->readUint16LE(); // x,y skip

	if (stringsCount == 0)
		error("opSpeak stringsCount == 0");

	if (stringsCount > ACTOR_SPEECH_STRING_MAX)
		error("opSpeak stringsCount=0x%X exceed ACTOR_SPEECH_STRING_MAX", stringsCount);

	int16 iparam1 = first = thread->stackTop();
	for (int i = 0; i < stringsCount; i++) {
		iparam1 = thread->pop();
		strings[i] = thread->_strings->getString(iparam1);
	}

	// now data contains last string index

#if 0
	if (_vm->getFeatures() & GF_OLD_ITE_DOS) { // special ITE dos
		if ((_vm->_scene->currentSceneNumber() == ITE_DEFAULT_SCENE) &&
			(iparam1 >= 288) && (iparam1 <= (RID_SCENE1_VOICE_END - RID_SCENE1_VOICE_START + 288))) {
			sampleResourceId = RID_SCENE1_VOICE_START + iparam1 - 288;
		}
	} else {
#endif
		if (thread->_voiceLUT->size() > uint16(first))
			sampleResourceId = (*thread->_voiceLUT)[uint16(first)];
#if 0
	}
#endif

	if (sampleResourceId < 0 || sampleResourceId > 4000)
		sampleResourceId = -1;

	if (_vm->getGameId() == GID_ITE && !sampleResourceId)
		sampleResourceId = -1;

	_vm->_actor->actorSpeech(actorId, strings, stringsCount, sampleResourceId, speechFlags);

	if (!(speechFlags & kSpeakAsync)) {
		thread->wait(kWaitTypeSpeech);
	}
}

void Script::opDialogBegin(SCRIPTOP_PARAMS) {
	if (_conversingThread) {
		thread->wait(kWaitTypeDialogBegin);
		stopParsing = true;
		breakOut = false;
		return;
	}
	_conversingThread = thread;
	_vm->_interface->converseClear();
}

void Script::opDialogEnd(SCRIPTOP_PARAMS) {
	if (thread == _conversingThread) {
		_vm->_interface->activate();
		_vm->_interface->setMode(kPanelConverse);
		thread->wait(kWaitTypeDialogEnd);
		stopParsing = true;
		breakOut = false;
		return;
	}
}

void Script::opReply(SCRIPTOP_PARAMS) {
	const char *str;
	byte replyNum = scriptS->readByte();
	byte flags = scriptS->readByte();
	int16 iparam1 = 0;
	int strID = thread->pop();

	if (flags & kReplyOnce) {
		iparam1 = scriptS->readSint16LE();
		byte *addr = thread->_staticBase + (iparam1 >> 3);
		if (*addr & (1 << (iparam1 & 7))) {
			return;
		}
	}

	str = thread->_strings->getString(strID);
	if (_vm->_interface->converseAddText(str, strID, replyNum, flags, iparam1))
		warning("Error adding ConverseText (%s, %d, %d, %d)", str, replyNum, flags, iparam1);
}

void Script::opAnimate(SCRIPTOP_PARAMS) {
	scriptS->readUint16LE();
	scriptS->readUint16LE();
	thread->_instructionOffset += scriptS->readByte();
}

void Script::opJmpSeedRandom(SCRIPTOP_PARAMS) {
	// SAGA 2 opcode
	// TODO
	warning("opJmpSeedRandom");
}

void Script::loadModule(uint scriptModuleNumber) {
	ByteArray resourceData;

	// Validate script number
	if (scriptModuleNumber >= _modules.size()) {
		error("Script::loadScript() Invalid script module number");
	}

	if (_modules[scriptModuleNumber].loaded) {
		return;
	}

	// Initialize script data structure
	debug(3, "Loading script module #%d", scriptModuleNumber);

	_vm->_resource->loadResource(_scriptContext, _modules[scriptModuleNumber].scriptResourceId, resourceData);

	loadModuleBase(_modules[scriptModuleNumber], resourceData);

	_vm->_resource->loadResource(_scriptContext, _modules[scriptModuleNumber].stringsResourceId, resourceData);

	_vm->loadStrings(_modules[scriptModuleNumber].strings, resourceData);

	if (_modules[scriptModuleNumber].voicesResourceId > 0) {
		_vm->_resource->loadResource(_scriptContext, _modules[scriptModuleNumber].voicesResourceId, resourceData);

		loadVoiceLUT(_modules[scriptModuleNumber].voiceLUT, resourceData);
	}

	_modules[scriptModuleNumber].staticOffset = _staticSize;
	_staticSize += _modules[scriptModuleNumber].staticSize;
	if (_staticSize > _commonBuffer.size()) {
		error("Script::loadModule() _staticSize > _commonBuffer.size()");
	}
	_modules[scriptModuleNumber].loaded = true;
}

void Script::clearModules() {
	uint i;
	for (i = 0; i < _modules.size(); i++) {
		if (_modules[i].loaded) {
			_modules[i].clear();
		}
	}
	_staticSize = 0;
}

void Script::loadModuleBase(ModuleData &module, const ByteArray &resourceData) {
	uint i;

	debug(3, "Loading module base...");

	module.moduleBase.assign(resourceData);

	ByteArrayReadStreamEndian scriptS(module.moduleBase, _scriptContext->isBigEndian());

	uint entryPointsCount = scriptS.readUint16();
	scriptS.readUint16(); //skip
	uint16 entryPointsTableOffset;	// offset of entrypoint table in moduleBase
	entryPointsTableOffset = scriptS.readUint16();
	scriptS.readUint16(); //skip

	if ((module.moduleBase.size() - entryPointsTableOffset) < (entryPointsCount * SCRIPT_TBLENTRY_LEN)) {
		error("Script::loadModuleBase() Invalid table offset");
	}

	if (entryPointsCount > SCRIPT_MAX) {
		error("Script::loadModuleBase()Script limit exceeded");
	}

	module.entryPoints.resize(entryPointsCount);

	// Read in the entrypoint table

	module.staticSize = scriptS.readUint16();
	while (scriptS.pos() < entryPointsTableOffset)
		scriptS.readByte();

	for (i = 0; i < module.entryPoints.size(); i++) {
		// First uint16 is the offset of the entrypoint name from the start
		// of the bytecode resource, second uint16 is the offset of the
		// bytecode itself for said entrypoint
		module.entryPoints[i].nameOffset = scriptS.readUint16();
		module.entryPoints[i].offset = scriptS.readUint16();

		// Perform a simple range check on offset values
		if ((module.entryPoints[i].nameOffset >= module.moduleBase.size()) || (module.entryPoints[i].offset >= module.moduleBase.size())) {
			error("Script::loadModuleBase() Invalid offset encountered in script entrypoint table");
		}
	}
}

void Script::loadVoiceLUT(VoiceLUT &voiceLUT, const ByteArray &resourceData) {
	uint16 i;

	voiceLUT.resize(resourceData.size() / 2);

	ByteArrayReadStreamEndian scriptS(resourceData, _scriptContext->isBigEndian());

	for (i = 0; i < voiceLUT.size(); i++) {
		voiceLUT[i] = scriptS.readUint16();
	}
}

// verb
void Script::showVerb(int statusColor) {
	const char *verbName;
	const char *object1Name;
	const char *object2Name;
	Common::String statusString;

	if (_leftButtonVerb == getVerbType(kVerbNone)) {
		_vm->_interface->setStatusText("");
		return;
	}

	if (_vm->getGameId() == GID_ITE)
		verbName = _mainStrings.getString(_leftButtonVerb - 1);
	else
		verbName = _mainStrings.getString(_leftButtonVerb + 1);

	if (objectTypeId(_currentObject[0]) == kGameObjectNone) {
		_vm->_interface->setStatusText(verbName, statusColor);
		return;
	}

	object1Name = _vm->getObjectName(_currentObject[0]);

	if (!_secondObjectNeeded) {
		statusString = Common::String::format("%s %s", verbName, object1Name);
		_vm->_interface->setStatusText(statusString.c_str(), statusColor);
		return;
	}


	if (objectTypeId(_currentObject[1]) != kGameObjectNone) {
		object2Name = _vm->getObjectName(_currentObject[1]);
	} else {
		object2Name = "";
	}

	if (_leftButtonVerb == getVerbType(kVerbGive)) {
		statusString = Common::String::format(_vm->getTextString(kTextGiveTo), object1Name, object2Name);
		_vm->_interface->setStatusText(statusString.c_str(), statusColor);
	} else {
		if (_leftButtonVerb == getVerbType(kVerbUse)) {
			statusString = Common::String::format(_vm->getTextString(kTextUseWidth), object1Name, object2Name);
			_vm->_interface->setStatusText(statusString.c_str(), statusColor);
		} else {
			statusString = Common::String::format("%s %s", verbName, object1Name);
			_vm->_interface->setStatusText(statusString.c_str(), statusColor);
		}
	}
}

int Script::getVerbType(VerbTypes verbType) {
	if (_vm->getGameId() == GID_ITE) {
		switch (verbType) {
		case kVerbNone:
			return kVerbITENone;
		case kVerbWalkTo:
			return kVerbITEWalkTo;
		case kVerbGive:
			return kVerbITEGive;
		case kVerbUse:
			return kVerbITEUse;
		case kVerbEnter:
			return kVerbITEEnter;
		case kVerbLookAt:
			return kVerbITELookAt;
		case kVerbPickUp:
			return kVerbITEPickUp;
		case kVerbOpen:
			return kVerbITEOpen;
		case kVerbClose:
			return kVerbITEClose;
		case kVerbTalkTo:
			return kVerbITETalkTo;
		case kVerbWalkOnly:
			return kVerbITEWalkOnly;
		case kVerbLookOnly:
			return kVerbITELookOnly;
		case kVerbOptions:
			return kVerbITEOptions;
		}
#ifdef ENABLE_IHNM
	} else if (_vm->getGameId() == GID_IHNM) {
		switch (verbType) {
		case kVerbNone:
			return kVerbIHNMNone;
		case kVerbWalkTo:
			return kVerbIHNMWalk;
		case kVerbLookAt:
			return kVerbIHNMLookAt;
		case kVerbPickUp:
			return kVerbIHNMTake;
		case kVerbUse:
			return kVerbIHNMUse;
		case kVerbTalkTo:
			return kVerbIHNMTalkTo;
		case kVerbOpen:
			return kVerbIHNMSwallow;
		case kVerbGive:
			return kVerbIHNMGive;
		case kVerbClose:
			return kVerbIHNMPush;
		case kVerbEnter:
			return kVerbIHNMEnter;
		case kVerbWalkOnly:
			return kVerbIHNMWalkOnly;
		case kVerbLookOnly:
			return kVerbIHNMLookOnly;
		case kVerbOptions:
			return kVerbIHNMOptions;
		}
#endif
	}
	error("Script::getVerbType() unknown verb type %d", verbType);
}

void Script::setVerb(int verb) {
	_pendingObject[0] = ID_NOTHING;
	_currentObject[0] = ID_NOTHING;
	_pendingObject[1] = ID_NOTHING;
	_currentObject[1] = ID_NOTHING;
	_firstObjectSet = false;
	_secondObjectNeeded = false;

	// The pointer object will be updated again immediately. This way the
	// new verb will be applied to it. It's not exactly how the original
	// engine did it, but it appears to work.
	_pointerObject = ID_NOTHING;

	setLeftButtonVerb(verb);
	showVerb();
}

bool Script::isNonInteractiveDemo() {
	// This detection only works in ITE. The early non-interactive demos had
	// a very small script file
	return _vm->getGameId() == GID_ITE && _scriptContext->fileSize() < 50000;
}

void Script::setLeftButtonVerb(int verb) {
	int		oldVerb = _currentVerb;

	_currentVerb = _leftButtonVerb = verb;

	if ((_currentVerb != oldVerb) && (_vm->_interface->getMode() == kPanelMain)){
			if (oldVerb > getVerbType(kVerbNone))
				_vm->_interface->setVerbState(oldVerb, 2);

			if (_currentVerb > getVerbType(kVerbNone))
				_vm->_interface->setVerbState(_currentVerb, 2);
	}
}

void Script::setRightButtonVerb(int verb) {
	int		oldVerb = _rightButtonVerb;

	_rightButtonVerb = verb;

	if ((_rightButtonVerb != oldVerb) && (_vm->_interface->getMode() == kPanelMain)){
		if (oldVerb > getVerbType(kVerbNone))
			_vm->_interface->setVerbState(oldVerb, 2);

		if (_rightButtonVerb > getVerbType(kVerbNone))
			_vm->_interface->setVerbState(_rightButtonVerb, 2);
	}
}

void Script::doVerb() {
	int scriptEntrypointNumber = 0;
	int scriptModuleNumber = 0;
	int objectType;
	Event event;
	const char *excuseText;
	int excuseSampleResourceId;
	const HitZone *hitZone;

	objectType = objectTypeId(_pendingObject[0]);

	if (_pendingVerb == getVerbType(kVerbGive)) {
		scriptEntrypointNumber = _vm->_actor->getObjectScriptEntrypointNumber(_pendingObject[1]);
		if (_vm->_actor->getObjectFlags(_pendingObject[1]) & (kFollower|kProtagonist|kExtended)) {
			scriptModuleNumber = 0;
		} else {
			scriptModuleNumber = _vm->_scene->getScriptModuleNumber();
		}
		// IHNM never sets scriptModuleNumber to 0
		if (_vm->getGameId() == GID_IHNM)
			scriptModuleNumber = _vm->_scene->getScriptModuleNumber();
	} else {
		if (_pendingVerb == getVerbType(kVerbUse)) {
			if ((objectTypeId(_pendingObject[1]) > kGameObjectNone) && (objectType < objectTypeId(_pendingObject[1]))) {
				SWAP(_pendingObject[0], _pendingObject[1]);
				objectType = objectTypeId(_pendingObject[0]);
			}
		}

		if (objectType == 0)
			return;
		else if (objectType == kGameObjectHitZone) {
			scriptModuleNumber = _vm->_scene->getScriptModuleNumber();
			hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(_pendingObject[0]));

			if (hitZone == NULL)
				return;

			if ((hitZone->getFlags() & kHitZoneExit) == 0) {
				scriptEntrypointNumber = hitZone->getScriptNumber();
			}
		} else {
			if (objectType & (kGameObjectActor | kGameObjectObject)) {
				scriptEntrypointNumber = _vm->_actor->getObjectScriptEntrypointNumber(_pendingObject[0]);

				if ((objectType == kGameObjectActor) && !(_vm->_actor->getObjectFlags(_pendingObject[0]) & (kFollower|kProtagonist|kExtended))) {
					scriptModuleNumber = _vm->_scene->getScriptModuleNumber();
				} else {
					scriptModuleNumber = 0;
				}
				// IHNM never sets scriptModuleNumber to 0
				if (_vm->getGameId() == GID_IHNM)
					scriptModuleNumber = _vm->_scene->getScriptModuleNumber();
			}
		}
	}

	// WORKAROUND for a bug in the original game scripts of IHNM. Edna's script (actor 8197) is problematic, so
	// when the knife (object 16385) is used with her, the expected result is not correct. The first time that
	// the knife is used, Edna's heart is cut out (which is correct). But on subsequent use, the object's script
	// is buggy, therefore it's possible to talk to a dead Edna by using the knife on her, or to incorrectly get her
	// heart again, which remove's Gorrister's heart from the inventory. The solution is to disable the "use knife with
	// Edna" action altogether, because if the player wants to kill Edna, he can do that by talking to her and
	// choosing "[Cut out Edna's heart]", which works correctly. To disable this action, if the knife is used on Edna, we
	// change the action here to "use knife with the knife", which yields a better reply ("I'd just dull my knife").
	// Fixes bug #1826871 - "IHNM: Edna's got two hearts but loves to be on the hook"
	if (_vm->getGameId() == GID_IHNM && _pendingObject[0] == 16385 && _pendingObject[1] == 8197 && _pendingVerb == 4)
		_pendingObject[1] = 16385;

	// WORKAROUND for a bug in the original game scripts of IHNM. Gorrister's heart is not supposed to have a
	// "use" phrase attached to it (it's not used anywhere, it's only given), but when "used", an incorrect
	// reply is given to the player ("It's too narrow for me to pass", said when Gorrister tries to pick up the
	// heart without a rope). Therefore, for object number 16397 (Gorrister's heart), when the active verb is
	// "Use", set it to "Push", which gives a more appropriate reply ("What good will that do me?")
	if (_vm->getGameId() == GID_IHNM && _pendingObject[0] == 16397 && _pendingVerb == 4)
		_pendingVerb = 8;

	if (scriptEntrypointNumber > 0) {

		event.type = kEvTOneshot;
		event.code = kScriptEvent;
		event.op = kEventExecNonBlocking;
		event.time = 0;
		event.param = scriptModuleNumber;
		event.param2 = scriptEntrypointNumber;
		event.param3 = _pendingVerb;		// Action
		event.param4 = _pendingObject[0];	// Object
		event.param5 = _pendingObject[1];	// With Object
		event.param6 = (objectType == kGameObjectActor) ? _pendingObject[0] : ID_PROTAG;		// Actor
		_vm->_events->queue(event);

	} else {
		// Show excuse text in ITE CD Versions
		if (_vm->getGameId() == GID_ITE) {
			_vm->getExcuseInfo(_pendingVerb, excuseText, excuseSampleResourceId);
			if (excuseText) {
				// In Floppy versions we don't have excuse texts
				if (_vm->getFeatures() & GF_ITE_FLOPPY)
					excuseSampleResourceId = -1;

				_vm->_actor->actorSpeech(ID_PROTAG, &excuseText, 1, excuseSampleResourceId, 0);
			}
		}
	}

	if ((_currentVerb == getVerbType(kVerbWalkTo)) || (_currentVerb == getVerbType(kVerbLookAt))) {
		_stickyVerb = _currentVerb;
	}

	_pendingVerb = getVerbType(kVerbNone);
	_currentObject[0] = _currentObject[1] = ID_NOTHING;
	setLeftButtonVerb(_stickyVerb);

	setPointerVerb();
}

void Script::setPointerVerb() {
	if (_vm->_interface->isActive()) {
		_pointerObject = ID_PROTAG;
		whichObject(_vm->mousePos());
	}
}

void Script::hitObject(bool leftButton) {
	int verb;
	verb = leftButton ? _leftButtonVerb : _rightButtonVerb;

	if (verb > getVerbType(kVerbNone)) {
		if (_firstObjectSet) {
			if (_secondObjectNeeded) {
				_pendingObject[0] = _currentObject[0];
				_pendingObject[1] = _currentObject[1];
				_pendingVerb = verb;

				_leftButtonVerb = verb;
				if (_pendingVerb > getVerbType(kVerbNone))
					showVerb(kITEColorBrightWhite);
				else
					showVerb();

				_secondObjectNeeded = false;
				_firstObjectSet = false;
				return;
			}
		} else {
			if (verb == getVerbType(kVerbGive)) {
				_secondObjectNeeded = true;
			} else {
				if (verb == getVerbType(kVerbUse)) {

					if (_currentObjectFlags[0] & kObjUseWith) {
						_secondObjectNeeded = true;
					}
				}
			}

			if (!_secondObjectNeeded) {
				_pendingObject[0] = _currentObject[0];
				_pendingObject[1] = ID_NOTHING;
				_pendingVerb = verb;

				_secondObjectNeeded = false;
				_firstObjectSet = false;
			} else {
				_firstObjectSet = true;
			}
		}

		_leftButtonVerb = verb;
		if (_pendingVerb > getVerbType(kVerbNone))
			showVerb(kITEColorBrightWhite);
		else
			showVerb();
	}

}

void Script::playfieldClick(const Point& mousePoint, bool leftButton) {
	Location pickLocation;
	const HitZone *hitZone;
	Point specialPoint;

	_vm->incrementMouseClickCount();
	_vm->_actor->abortSpeech();

	if ((_vm->_actor->_protagonist->_currentAction != kActionWait) &&
		(_vm->_actor->_protagonist->_currentAction != kActionFreeze) &&
		(_vm->_actor->_protagonist->_currentAction != kActionWalkToLink) &&
		(_vm->_actor->_protagonist->_currentAction != kActionWalkToPoint)) {
		return;
	}
	if (_pendingVerb > getVerbType(kVerbNone)) {
		setLeftButtonVerb(getVerbType(kVerbWalkTo));
	}

	if (_pointerObject != ID_NOTHING) {
		hitObject(leftButton);
	} else {
		_pendingObject[0] = ID_NOTHING;
		_pendingObject[1] = ID_NOTHING;
		_pendingVerb = getVerbType(kVerbWalkTo);
	}


	// tiled stuff
	if (_vm->_scene->getFlags() & kSceneFlagISO) {
		_vm->_isoMap->screenPointToTileCoords(mousePoint, pickLocation);
	} else {
		pickLocation.fromScreenPoint(mousePoint);
	}


	hitZone = NULL;

	if (objectTypeId(_pendingObject[0]) == kGameObjectHitZone) {
		 hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(_pendingObject[0]));
	} else {
		if ((_pendingVerb == getVerbType(kVerbUse)) && (objectTypeId(_pendingObject[1]) == kGameObjectHitZone)) {
			hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(_pendingObject[1]));
		}
	}

	if (hitZone != NULL) {
		if (_vm->getGameId() == GID_ITE) {
			if (hitZone->getFlags() & kHitZoneNoWalk) {
				_vm->_actor->actorFaceTowardsPoint(ID_PROTAG, pickLocation);
				doVerb();
				return;
			}
		} else {
			if (_vm->getGameId() == GID_IHNM) {
				if ((hitZone->getFlags() & kHitZoneNoWalk) && (_pendingVerb != getVerbType(kVerbWalkTo))) {
					doVerb();
					return;
				}
			}
		}

		if (hitZone->getFlags() & kHitZoneProject) {
			if (!hitZone->getSpecialPoint(specialPoint)) {
				// Original behaved this way and this prevents from crash
				// at ruins. See bug #1257459
				specialPoint.x = specialPoint.y = 0;
			}

			// tiled stuff
			if (_vm->_scene->getFlags() & kSceneFlagISO) {
				pickLocation.u() = specialPoint.x;
				pickLocation.v() = specialPoint.y;
				pickLocation.z = _vm->_actor->_protagonist->_location.z;
			} else {
				pickLocation.fromScreenPoint(specialPoint);
			}
		}
	}

	if (_vm->getGameId() == GID_ITE) {
		if ((_pendingVerb == getVerbType(kVerbWalkTo)) ||
			(_pendingVerb == getVerbType(kVerbPickUp)) ||
			(_pendingVerb == getVerbType(kVerbOpen)) ||
			(_pendingVerb == getVerbType(kVerbClose)) ||
			(_pendingVerb == getVerbType(kVerbUse))) {
				_vm->_actor->actorWalkTo(ID_PROTAG, pickLocation);
		} else {
			if (_pendingVerb == getVerbType(kVerbLookAt)) {
				if (objectTypeId(_pendingObject[0]) != kGameObjectActor) {
					_vm->_actor->actorWalkTo(ID_PROTAG, pickLocation);
				} else {
					doVerb();
				}
			} else {
				if ((_pendingVerb == getVerbType(kVerbTalkTo)) ||
					(_pendingVerb == getVerbType(kVerbGive))) {
						doVerb();
				}
			}
		}
	}

#ifdef ENABLE_IHNM
	if (_vm->getGameId() == GID_IHNM) {

		if ((_pendingVerb == getVerbType(kVerbWalkTo)) ||
			(_pendingVerb == getVerbType(kVerbPickUp)) ||
			(_pendingVerb == getVerbType(kVerbOpen)) ||
			(_pendingVerb == getVerbType(kVerbClose)) ||
			(_pendingVerb == getVerbType(kVerbUse))) {
				_vm->_actor->actorWalkTo(ID_PROTAG, pickLocation);

				// Auto-use no-walk hitzones in IHNM, needed for Benny's chapter
				if (_pendingVerb == getVerbType(kVerbWalkTo) &&
					hitZone != NULL && (hitZone->getFlags() & kHitZoneNoWalk)) {
					_pendingVerb = getVerbType(kVerbUse);
					if (objectTypeId(_pendingObject[0]) == kGameObjectActor) {
						_vm->_actor->actorFaceTowardsObject(ID_PROTAG, _pendingObject[0]);
						doVerb();
					}
				}

				// Auto-use hitzone with id 24576 (the exit to the left) in screens 16 - 19
				// (screens with Gorrister's heart) in IHNM. For some reason, this zone does
				// not have a corresponding action zone, so we auto-use it here, like the exits
				// in Benny's chapter
				if (_vm->_scene->currentChapterNumber() == 1 &&
					_vm->_scene->currentSceneNumber() >= 16 &&
					_vm->_scene->currentSceneNumber() <= 19 &&
					_pendingVerb == getVerbType(kVerbWalkTo) &&
					hitZone != NULL && hitZone->getHitZoneId() == 24576) {
					_pendingVerb = getVerbType(kVerbUse);
					if (objectTypeId(_pendingObject[0]) == kGameObjectActor) {
						_vm->_actor->actorFaceTowardsObject(ID_PROTAG, _pendingObject[0]);
						doVerb();
					}
				}

		} else {
			if (_pendingVerb == getVerbType(kVerbLookAt)) {
				if (objectTypeId(_pendingObject[0]) != kGameObjectActor) {
					_vm->_actor->actorWalkTo(ID_PROTAG, pickLocation);
				} else {
					_vm->_actor->actorFaceTowardsObject(ID_PROTAG, _pendingObject[0]);
					doVerb();
				}
			} else {
				if ((_pendingVerb == getVerbType(kVerbTalkTo)) ||
					(_pendingVerb == getVerbType(kVerbGive))) {
						doVerb();
				}
			}
		}
	}
#endif

}

void Script::whichObject(const Point& mousePoint) {
	uint16 objectId;
	int16 objectFlags;
	int newRightButtonVerb;
	uint16 newObjectId;
	ActorData *actor;
	ObjectData *obj;
	Point pickPoint;
	Location pickLocation;
	int hitZoneIndex;
	const HitZone * hitZone;
	PanelButton * panelButton;

	objectId = ID_NOTHING;
	objectFlags = 0;
	_leftButtonVerb = _currentVerb;
	newRightButtonVerb = getVerbType(kVerbNone);

	// _protagonist can be null while loading a game from the command line
	if (_vm->_actor->_protagonist == NULL)
		return;

	if (_vm->_actor->_protagonist->_currentAction != kActionWalkDir) {
		if (_vm->_scene->getHeight() >= mousePoint.y) {
			newObjectId = _vm->_actor->hitTest(mousePoint, true);

			if (newObjectId != ID_NOTHING) {
				if (objectTypeId(newObjectId) == kGameObjectObject) {
					objectId = newObjectId;
					objectFlags = 0;
					newRightButtonVerb = getVerbType(kVerbLookAt);

					if ((_currentVerb == getVerbType(kVerbTalkTo)) || ((_currentVerb == getVerbType(kVerbGive)) && _firstObjectSet)) {
						objectId = ID_NOTHING;
						newObjectId = ID_NOTHING;
					}
				} else {
					actor = _vm->_actor->getActor(newObjectId);
					objectId = newObjectId;
					if (_vm->getGameId() == GID_ITE)
						objectFlags = kObjUseWith;
					// Note: for IHNM, the default right button action is "Look at" for actors,
					// but "Talk to" makes much more sense
					newRightButtonVerb = getVerbType(kVerbTalkTo);
					// Slight hack because of the above change: the jukebox in Gorrister's chapter
					// is an actor, so change the right button action to "Look at"
					if (_vm->getGameId() == GID_IHNM && objectId == 8199)
						newRightButtonVerb = getVerbType(kVerbLookAt);

					if ((_currentVerb == getVerbType(kVerbPickUp)) ||
						(_currentVerb == getVerbType(kVerbOpen)) ||
						(_currentVerb == getVerbType(kVerbClose)) ||
						((_currentVerb == getVerbType(kVerbGive)) && !_firstObjectSet) ||
						((_currentVerb == getVerbType(kVerbUse)) && !(actor->_flags & kFollower))) {
							if (_vm->getGameId() == GID_ITE) {
								objectId = ID_NOTHING;
								newObjectId = ID_NOTHING;
							}
						}
				}
			}

			if (newObjectId == ID_NOTHING) {

				pickPoint = mousePoint;

				if (_vm->_scene->getFlags() & kSceneFlagISO) {
					pickPoint.y -= _vm->_actor->_protagonist->_location.z;
					_vm->_isoMap->screenPointToTileCoords(pickPoint, pickLocation);
					pickLocation.toScreenPointUV(pickPoint);
				}

				hitZoneIndex = _vm->_scene->_objectMap->hitTest(pickPoint);

				// WORKAROUND for an incorrect hitzone which exists in IHNM
				// In Gorrister's chapter, in the toilet screen, the hitzone of the exit is
				// placed over the place where Gorrister sits to examine the graffiti on the wall
				// to the left, which makes him exit the screen when the graffiti is examined.
				// We effectively change the left side of the hitzone here so that it starts from
				// pixel 301 onwards. The same workaround is applied in Actor::handleActions
				if (_vm->getGameId() == GID_IHNM) {
					if (_vm->_scene->currentChapterNumber() == 1 && _vm->_scene->currentSceneNumber() == 22)
						if (hitZoneIndex == 8 && pickPoint.x <= 300)
							hitZoneIndex = -1;
				}

				if ((hitZoneIndex != -1)) {
					hitZone = _vm->_scene->_objectMap->getHitZone(hitZoneIndex);
					objectId = hitZone->getHitZoneId();
					objectFlags = 0;
					newRightButtonVerb = hitZone->getRightButtonVerb() & 0x7f;

					// WORKAROUND for a problematic object in IHNM
					// In the freezer room, the key that drops is made of a hitzone which
					// contains the key object itself. We change the object ID that the
					// hitzone contains (object ID 24578 - "The key") to the ID of the key
					// object itself (object ID 16402 - "Edna's key"), as the user can keep
					// hovering the cursor to both items, but can only pick up one
					if (_vm->getGameId() == GID_IHNM) {
						if (_vm->_scene->currentChapterNumber() == 1 && _vm->_scene->currentSceneNumber() == 24) {
							if (objectId == 24578)
								objectId = 16402;
						}
					}

					if (_vm->getGameId() == GID_ITE) {

						if (newRightButtonVerb == getVerbType(kVerbWalkOnly)) {
							if (_firstObjectSet) {
								objectId = ID_NOTHING;
							} else {
								newRightButtonVerb = _leftButtonVerb = getVerbType(kVerbWalkTo);
							}
						} else {
							if (newRightButtonVerb == getVerbType(kVerbLookOnly)) {
								if (_firstObjectSet) {
									objectId = ID_NOTHING;
								} else {
									newRightButtonVerb = _leftButtonVerb = getVerbType(kVerbLookAt);
								}
							}
						}

						if (newRightButtonVerb >= getVerbType(kVerbOptions)) {
							newRightButtonVerb = getVerbType(kVerbNone);
						}
					} else {
						if (newRightButtonVerb >= getVerbType(kVerbOptions)) {
							newRightButtonVerb = getVerbType(kVerbWalkTo);
						}
					}

					if ((_currentVerb == getVerbType(kVerbTalkTo)) || ((_currentVerb == getVerbType(kVerbGive)) && _firstObjectSet)) {
						objectId = ID_NOTHING;
						newObjectId = ID_NOTHING;
					}

					if ((_leftButtonVerb == getVerbType(kVerbUse)) && (hitZone->getRightButtonVerb() & 0x80)) {
						objectFlags = kObjUseWith;
					}
				}
			}
		} else {
			if ((_currentVerb == getVerbType(kVerbTalkTo)) || ((_currentVerb == getVerbType(kVerbGive)) && _firstObjectSet)) {
				// no way
			} else {
				panelButton = _vm->_interface->inventoryHitTest(mousePoint);
				if (panelButton) {
					objectId = _vm->_interface->getInventoryContentByPanelButton(panelButton);
					if (objectId != 0) {
						obj = _vm->_actor->getObj(objectId);
						newRightButtonVerb = getVerbType(kVerbLookAt);
						if (obj->_interactBits & kObjUseWith) {
							objectFlags = kObjUseWith;
						}
					}
				}
			}

			if ((_currentVerb == getVerbType(kVerbPickUp)) || (_currentVerb == getVerbType(kVerbTalkTo)) || (_currentVerb == getVerbType(kVerbWalkTo))) {
				_leftButtonVerb = getVerbType(kVerbLookAt);
			}
		}
	}

	if (objectId != _pointerObject) {
		_pointerObject = objectId;
		_currentObject[_firstObjectSet ? 1 : 0] = objectId;
		_currentObjectFlags[_firstObjectSet ? 1 : 0] = objectFlags;
		if (_pendingVerb == getVerbType(kVerbNone)) {
			showVerb();
		}
	}

	if (newRightButtonVerb != _rightButtonVerb) {
		setRightButtonVerb(newRightButtonVerb);
	}
}

} // End of namespace Saga
back to top