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

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

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-12-0/engines/saga/sfuncs.cpp $
 * $Id: sfuncs.cpp 32282 2008-05-26 11:10:46Z thebluegr $
 *
 */

// Scripting module script function component

#include "saga/saga.h"

#include "saga/gfx.h"
#include "saga/actor.h"
#include "saga/animation.h"
#include "saga/console.h"
#include "saga/events.h"
#include "saga/font.h"
#include "saga/interface.h"
#include "saga/music.h"
#include "saga/itedata.h"
#include "saga/puzzle.h"
#include "saga/render.h"
#include "saga/sound.h"
#include "saga/sndres.h"
#include "saga/rscfile.h"

#include "saga/script.h"
#include "saga/objectmap.h"

#include "saga/scene.h"
#include "saga/isomap.h"

#include "common/config-manager.h"

namespace Saga {

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

void Script::setupScriptFuncList(void) {
	static const ScriptFunctionDescription ITEscriptFunctionsList[ITE_SCRIPT_FUNCTION_MAX] = {
		OPCODE(sfPutString),
		OPCODE(sfWait),
		OPCODE(sfTakeObject),
		OPCODE(sfIsCarried),
		OPCODE(sfStatusBar),
		OPCODE(sfMainMode),
		OPCODE(sfScriptWalkTo),
		OPCODE(sfScriptDoAction),
		OPCODE(sfSetActorFacing),
		OPCODE(sfStartBgdAnim),
		OPCODE(sfStopBgdAnim),
		OPCODE(sfLockUser),
		OPCODE(sfPreDialog),
		OPCODE(sfKillActorThreads),
		OPCODE(sfFaceTowards),
		OPCODE(sfSetFollower),
		OPCODE(sfScriptGotoScene),
		OPCODE(sfSetObjImage),
		OPCODE(sfSetObjName),
		OPCODE(sfGetObjImage),
		OPCODE(sfGetNumber),
		OPCODE(sfScriptOpenDoor),
		OPCODE(sfScriptCloseDoor),
		OPCODE(sfSetBgdAnimSpeed),
		OPCODE(SF_cycleColors),
		OPCODE(sfDoCenterActor),
		OPCODE(sfStartBgdAnimSpeed),
		OPCODE(sfScriptWalkToAsync),
		OPCODE(sfEnableZone),
		OPCODE(sfSetActorState),
		OPCODE(sfScriptMoveTo),
		OPCODE(sfSceneEq),
		OPCODE(sfDropObject),
		OPCODE(sfFinishBgdAnim),
		OPCODE(sfSwapActors),
		OPCODE(sfSimulSpeech),
		OPCODE(sfScriptWalk),
		OPCODE(sfCycleFrames),
		OPCODE(sfSetFrame),
		OPCODE(sfSetPortrait),
		OPCODE(sfSetProtagPortrait),
		OPCODE(sfChainBgdAnim),
		OPCODE(sfScriptSpecialWalk),
		OPCODE(sfPlaceActor),
		OPCODE(sfCheckUserInterrupt),
		OPCODE(sfScriptWalkRelative),
		OPCODE(sfScriptMoveRelative),
		OPCODE(sfSimulSpeech2),
		OPCODE(sfPlacard),
		OPCODE(sfPlacardOff),
		OPCODE(sfSetProtagState),
		OPCODE(sfResumeBgdAnim),
		OPCODE(sfThrowActor),
		OPCODE(sfWaitWalk),
		OPCODE(sfScriptSceneID),
		OPCODE(sfChangeActorScene),
		OPCODE(sfScriptClimb),
		OPCODE(sfSetDoorState),
		OPCODE(sfSetActorZ),
		OPCODE(sfScriptText),
		OPCODE(sfGetActorX),
		OPCODE(sfGetActorY),
		OPCODE(sfEraseDelta),
		OPCODE(sfPlayMusic),
		OPCODE(sfPickClimbOutPos),
		OPCODE(sfTossRif),
		OPCODE(sfShowControls),
		OPCODE(sfShowMap),
		OPCODE(sfPuzzleWon),
		OPCODE(sfEnableEscape),
		OPCODE(sfPlaySound),
		OPCODE(sfPlayLoopedSound),
		OPCODE(sfGetDeltaFrame),
		OPCODE(sfShowProtect),
		OPCODE(sfProtectResult),
		OPCODE(sfRand),
		OPCODE(sfFadeMusic),
		OPCODE(sfPlayVoice)
	};

static const ScriptFunctionDescription IHNMscriptFunctionsList[IHNM_SCRIPT_FUNCTION_MAX] = {
		OPCODE(sfNull),
		OPCODE(sfWait),
		OPCODE(sfTakeObject),
		OPCODE(sfIsCarried),
		OPCODE(sfStatusBar),
		OPCODE(sfMainMode),
		OPCODE(sfScriptWalkTo),
		OPCODE(sfScriptDoAction),
		OPCODE(sfSetActorFacing),
		OPCODE(sfStartBgdAnim),
		OPCODE(sfStopBgdAnim),
		OPCODE(sfLockUser),
		OPCODE(sfPreDialog),
		OPCODE(sfKillActorThreads),
		OPCODE(sfFaceTowards),
		OPCODE(sfSetFollower),
		OPCODE(sfScriptGotoScene),
		OPCODE(sfSetObjImage),
		OPCODE(sfSetObjName),
		OPCODE(sfGetObjImage),
		OPCODE(sfGetNumber),
		OPCODE(sfScriptOpenDoor),
		OPCODE(sfScriptCloseDoor),
		OPCODE(sfSetBgdAnimSpeed),
		OPCODE(SF_cycleColors),
		OPCODE(sfDoCenterActor),
		OPCODE(sfStartBgdAnimSpeed),
		OPCODE(sfScriptWalkToAsync),
		OPCODE(sfEnableZone),
		OPCODE(sfSetActorState),
		OPCODE(sfScriptMoveTo),
		OPCODE(sfSceneEq),
		OPCODE(sfDropObject),
		OPCODE(sfFinishBgdAnim),
		OPCODE(sfSwapActors),
		OPCODE(sfSimulSpeech),
		OPCODE(sfScriptWalk),
		OPCODE(sfCycleFrames),
		OPCODE(sfSetFrame),
		OPCODE(sfSetPortrait),
		OPCODE(sfSetProtagPortrait),
		OPCODE(sfChainBgdAnim),
		OPCODE(sfScriptSpecialWalk),
		OPCODE(sfPlaceActor),
		OPCODE(sfCheckUserInterrupt),
		OPCODE(sfScriptWalkRelative),
		OPCODE(sfScriptMoveRelative),
		OPCODE(sfSimulSpeech2),
		OPCODE(sfPsychicProfile),
		OPCODE(sfPsychicProfileOff),
		OPCODE(sfSetProtagState),
		OPCODE(sfResumeBgdAnim),
		OPCODE(sfThrowActor),
		OPCODE(sfWaitWalk),
		OPCODE(sfScriptSceneID),
		OPCODE(sfChangeActorScene),
		OPCODE(sfScriptClimb),
		OPCODE(sfSetDoorState),
		OPCODE(sfSetActorZ),
		OPCODE(sfScriptText),
		OPCODE(sfGetActorX),
		OPCODE(sfGetActorY),
		OPCODE(sfEraseDelta),
		OPCODE(sfPlayMusic),
		OPCODE(sfNull),
		OPCODE(sfEnableEscape),
		OPCODE(sfPlaySound),
		OPCODE(sfPlayLoopedSound),
		OPCODE(sfGetDeltaFrame),
		OPCODE(sfNull),
		OPCODE(sfNull),
		OPCODE(sfRand),
		OPCODE(sfFadeMusic),
		OPCODE(sfNull),
		OPCODE(sfSetChapterPoints),
		OPCODE(sfSetPortraitBgColor),
		OPCODE(sfScriptStartCutAway),
		OPCODE(sfReturnFromCutAway),
		OPCODE(sfEndCutAway),
		OPCODE(sfGetMouseClicks),
		OPCODE(sfResetMouseClicks),
		OPCODE(sfWaitFrames),
		OPCODE(sfScriptFade),
		OPCODE(sfScriptStartVideo),
		OPCODE(sfScriptReturnFromVideo),
		OPCODE(sfScriptEndVideo),
		OPCODE(sfSetActorZ),
		OPCODE(sfShowIHNMDemoHelpBg),
		OPCODE(sfAddIHNMDemoHelpTextLine),
		OPCODE(sfShowIHNMDemoHelpPage),
		OPCODE(sfVstopFX),
		OPCODE(sfVstopLoopedFX),
		OPCODE(sfDemoSetInteractive),	// only used in the demo version of IHNM
		OPCODE(sfDemoIsInteractive),
		OPCODE(sfVsetTrack),
		OPCODE(sfGetPoints),
		OPCODE(sfSetGlobalFlag),
		OPCODE(sfClearGlobalFlag),
		OPCODE(sfTestGlobalFlag),
		OPCODE(sfSetPoints),
		OPCODE(sfSetSpeechBox),
		OPCODE(sfDebugShowData),
		OPCODE(sfWaitFramesEsc),
		OPCODE(sfQueueMusic),
		OPCODE(sfDisableAbortSpeeches)
	};
	if (_vm->getGameType() == GType_IHNM)
		_scriptFunctionsList = IHNMscriptFunctionsList;
	else
		_scriptFunctionsList = ITEscriptFunctionsList;
}

// Script function #0 (0x00)
// Print a debugging message
void Script::sfPutString(SCRIPTFUNC_PARAMS) {
	const char *str = thread->_strings->getString(thread->pop());

	_vm->_console->DebugPrintf("sfPutString: %s\n",str);
	debug(0, "sfPutString: %s", str);
}

// Script function #1 (0x01) blocking
// Param1: time in ticks
void Script::sfWait(SCRIPTFUNC_PARAMS) {
	int16 time = thread->pop();

	if (!_skipSpeeches) {
		thread->waitDelay(_vm->ticksToMSec(time)); // put thread to sleep
	}
}

// Script function #2 (0x02)
void Script::sfTakeObject(SCRIPTFUNC_PARAMS) {
	uint16 objectId = thread->pop();
	ObjectData *obj = _vm->_actor->getObj(objectId);

	if (obj->_sceneNumber != ITE_SCENE_INV) {
		obj->_sceneNumber = ITE_SCENE_INV;

		// WORKAROUND for a problematic object in IHNM
		// There are 3 different scenes in front of the zeppelin, in Gorrister's chapter. A scene where the
		// zeppelin is in the air (scene 17), a scene where it approaches Gorrister's (scene 16) and another one
		// where it has landed (scene 18).
		// In two of these scenes (the "on air" and "approaching" ones), when the player uses the knife with the
		// rope, the associated script picks up object id 16392. In the "zeppelin landed" scene (scene 18), the
		// associated script picks up object id 16390. This seems to be a script bug, as it should be id 16392,
		// like in the other two scenes, as it is the same object (the rope). Picking up object 16390 leads to an
		// assertion anyway, therefore we change the problematic object (16390) to the correct one (16392) here.
		// Fixes bug #1861126 - "IHNM: Crash when Gorrister cuts sheet in the mooring ring"
		if (_vm->getGameType() == GType_IHNM) {
			if (_vm->_scene->currentChapterNumber() == 1 && _vm->_scene->currentSceneNumber() == 18) {
				if (objectId == 16390)
					objectId = 16392;
			}
		}

		// WORKAROUND for two incorrect object sprites in the IHNM demo
		// (the mirror and the icon in Ted's part). Set them correctly here
		if (_vm->getGameId() == GID_IHNM_DEMO) {
			if (objectId == 16408)
				obj->_spriteListResourceId = 24;
			if (objectId == 16409)
				obj->_spriteListResourceId = 25;
		}

		_vm->_interface->addToInventory(objectId);
	}
}

// Script function #3 (0x03)
// Check if an object is carried.
void Script::sfIsCarried(SCRIPTFUNC_PARAMS) {
	uint16 objectId = thread->pop();
	CommonObjectData *object;

	if (_vm->_actor->validObjId(objectId)) {
		object = _vm->_actor->getObj(objectId);
		thread->_returnValue = (object->_sceneNumber == ITE_SCENE_INV) ? 1 : 0;
	} else {
		thread->_returnValue = 0;
	}
}

// Script function #4 (0x04) nonblocking
// Set the command display to the specified text string
// Param1: dialogue index of string
void Script::sfStatusBar(SCRIPTFUNC_PARAMS) {
	_vm->_interface->setStatusText(thread->_strings->getString(thread->pop()));
}

// Script function #5 (0x05)
void Script::sfMainMode(SCRIPTFUNC_PARAMS) {
	_vm->_actor->_centerActor = _vm->_actor->_protagonist;

	showVerb();
	_vm->_interface->activate();
	_vm->_interface->setMode(kPanelMain);
	// Sometimes, the active cutaway is cleared after this opcode is called,
	// resulting in an incorrect mode being set. An example is Ellen's chapter
	// in IHNM, when using the computer with the chaos trebler CD. Make sure
	// that the saved mode is kPanelMain, so that it won't get overwritten
	// by an incorrect stored mode
	_vm->_interface->rememberMode();

	if (_vm->getGameType() == GType_ITE)
		setPointerVerb();

	// The early Windows and Mac demos of ITE were non-interactive. In those demos,
	// the intro is shown and then when the first scene is shown, there's a dialog
	// thanking the user for playing the demo and asking him to buy the full game,
	// without allowing him to continue any further. The game data itself for these
	// demos does not contain any scripts for the first scene (i.e. there's no text
	// in the game data to look at Rif's silver medallion). Also, there are no more
	// scenes apart from the Grand Tournament scene. This opcode is called in those
	// demos, and I assume that its use there is to just show the popup window and
	// exit the game. Therefore, once this opcode is called in the older ITE demos,
	// exit the game. Known non-interactive demos are GID_ITE_MACDEMO1 and
	// GID_ITE_WINDEMO1
	if (_vm->getFeatures() & GF_NON_INTERACTIVE)
		_vm->shutDown();
}

// Script function #6 (0x06) blocking
// Param1: actor id
// Param2: actor x
// Param3: actor y
void Script::sfScriptWalkTo(SCRIPTFUNC_PARAMS) {
	uint16 actorId = thread->pop();
	ActorData *actor = _vm->_actor->getActor(actorId);
	Location actorLocation;
	actorLocation.x = thread->pop();
	actorLocation.y = thread->pop();
	actorLocation.z = actor->_location.z;

	actor->_flags &= ~kFollower;

	if (_vm->_actor->actorWalkTo(actorId, actorLocation)) {
		thread->waitWalk(actor);
	}
}

// Script function #7 (0x07)
// Param1: actor id
// Param2: action
// Param3: theObject
// Param4: withObject
void Script::sfScriptDoAction(SCRIPTFUNC_PARAMS) {
	uint16 objectId = thread->pop();
	uint16 action = thread->pop();
	uint16 theObject = thread->pop();
	uint16 withObject = thread->pop();
	int16 scriptEntryPointNumber;
	int16 moduleNumber;
	ActorData *actor;
	ObjectData *obj;
	const HitZone *hitZone;
	Event event;

	// If the player uses an object and then immediately reuses that object
	// (without it being shown in the verb area), the object returned is wrong (0),
	// so we make it equal to the second object here.
	// Fixes bug #1861863 - "ITE: Crash when using Eeah with Eeah"
	if (theObject == 0 && objectId == 0 && withObject > 0)
		theObject = objectId = withObject;

	switch (objectTypeId(objectId)) {
		case kGameObjectObject:
			obj = _vm->_actor->getObj(objectId);
			scriptEntryPointNumber = obj->_scriptEntrypointNumber;
			if (scriptEntryPointNumber <= 0) {
				return;
			}
			moduleNumber = 0;
			if (_vm->getGameType() == GType_IHNM)
				moduleNumber = _vm->_scene->getScriptModuleNumber();
			break;
		case kGameObjectActor:
			actor = _vm->_actor->getActor(objectId);
			scriptEntryPointNumber = actor->_scriptEntrypointNumber;
			if (scriptEntryPointNumber <= 0) {
				return;
			}
			if (actor->_flags & (kProtagonist | kFollower)) {
				moduleNumber = 0;
			} else {
				moduleNumber = _vm->_scene->getScriptModuleNumber();
			}
			if (_vm->getGameType() == GType_IHNM)
				moduleNumber = _vm->_scene->getScriptModuleNumber();
			break;
		case kGameObjectHitZone:
		case kGameObjectStepZone:
			if (objectTypeId(objectId) == kGameObjectHitZone)
				hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(objectId));
			else
				hitZone = _vm->_scene->_actionMap->getHitZone(objectIdToIndex(objectId));

			if (hitZone == NULL)
				return;

			scriptEntryPointNumber = hitZone->getScriptNumber();
			moduleNumber = _vm->_scene->getScriptModuleNumber();
			break;
		default:
			// Unknown case, do nothing
			warning("Script::sfScriptDoAction wrong object type 0x%X", objectId);
			return;
	}

	event.type = kEvTOneshot;
	event.code = kScriptEvent;
	event.op = kEventExecNonBlocking;
	event.time = 0;
	event.param = moduleNumber;
	event.param2 = scriptEntryPointNumber;
	event.param3 = action;		// Action
	event.param4 = theObject;	// Object
	event.param5 = withObject;	// With Object
	event.param6 = objectId;
	_vm->_events->queue(&event);
}

// Script function #8 (0x08) nonblocking
// Param1: actor id
// Param2: actor orientation
void Script::sfSetActorFacing(SCRIPTFUNC_PARAMS) {
	ActorData *actor = _vm->_actor->getActor(thread->pop());
	int actorDirection = thread->pop();

	actor->_facingDirection = actor->_actionDirection = actorDirection;
	actor->_targetObject = ID_NOTHING;
}

// Script function #9 (0x09)
void Script::sfStartBgdAnim(SCRIPTFUNC_PARAMS) {
	int16 animId = thread->pop();
	int16 cycles = thread->pop();

	_vm->_anim->setCycles(animId, cycles);
	_vm->_anim->setFrameTime(animId, _vm->ticksToMSec(kRepeatSpeedTicks));

	if (!_vm->_anim->isPlaying(animId))
		_vm->_anim->play(animId, 0);

	debug(1, "sfStartBgdAnim(%d, %d)", animId, cycles);
}

// Script function #10 (0x0A)
void Script::sfStopBgdAnim(SCRIPTFUNC_PARAMS) {
	int16 animId = thread->pop();

	_vm->_anim->stop(animId);

	debug(1, "sfStopBgdAnim(%d)", animId);
}

// Script function #11 (0x0B) nonblocking
// If the parameter is true, the user interface is disabled while script
// continues to run. If the parameter is false, the user interface is
// reenabled.
// Param1: boolean
void Script::sfLockUser(SCRIPTFUNC_PARAMS) {
	if (thread->pop()) {
		_vm->_interface->deactivate();
	} else {
		_vm->_interface->activate();
	}

}

// Script function #12 (0x0C)
// Disables mouse input, etc.
void Script::sfPreDialog(SCRIPTFUNC_PARAMS) {
	_vm->_interface->deactivate();
	_vm->_interface->converseClear();

	if (_vm->_interface->isInMainMode())
		_vm->_interface->setMode(kPanelConverse);
	else
		_vm->_interface->converseDisplayText();

	_vm->_interface->setMode(kPanelNull);
}

// Script function #13 (0x0D)
void Script::sfKillActorThreads(SCRIPTFUNC_PARAMS) {
	ScriptThread *anotherThread;
	ScriptThreadList::iterator threadIterator;
	int16 actorId = thread->pop();

	for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) {
		anotherThread = threadIterator.operator->();
		if ((anotherThread != thread) && (anotherThread->_threadVars[kThreadVarActor] == actorId)) {
			anotherThread->_flags &= ~kTFlagWaiting;
			anotherThread->_flags |= kTFlagAborted;
		}
	}
}

// Script function #14 (0x0E)
// Param1: actor id
// Param2: object id
void Script::sfFaceTowards(SCRIPTFUNC_PARAMS) {
	ActorData *actor = _vm->_actor->getActor(thread->pop());
	actor->_targetObject = thread->pop();
}

// Script function #15 (0x0F)
// Param1: actor id
// Param2: target object
void Script::sfSetFollower(SCRIPTFUNC_PARAMS) {
	int16 actorId = thread->pop();
	ActorData *actor = _vm->_actor->getActor(actorId);
	actor->_targetObject = thread->pop();

	debug(1, "sfSetFollower(%d, %d) [%d]", actorId, actor->_targetObject, _vm->_actor->actorIdToIndex(actorId));

	if (actor->_targetObject != ID_NOTHING) {
		actor->_flags |= kFollower;
		actor->_actorFlags &= ~kActorNoFollow;
	} else {
		actor->_flags &= ~kFollower;
	}
}

// Script function #16 (0x10)
void Script::sfScriptGotoScene(SCRIPTFUNC_PARAMS) {
	int16 sceneNumber = thread->pop();
	int16 entrance = thread->pop();

	if (_vm->getGameType() == GType_IHNM) {
		_vm->_gfx->setCursor(kCursorBusy);
	}

	if (_vm->getGameType() == GType_ITE && sceneNumber < 0) {
		_vm->shutDown();
		return;
	}

	if (_vm->getGameType() == GType_IHNM && sceneNumber == 0) {
		_vm->_scene->creditsScene();
		return;
	}

	// It is possible to leave scene when converse panel is on,
	// particulalrly it may happen at Moneychanger tent. This
	// prevents this from happening.
	if (_vm->_interface->getMode() == kPanelConverse) {
		_vm->_interface->setMode(kPanelMain);
	}

	// changeScene calls loadScene which calls setVerb. setVerb resets all pending objects and object flags
	if (sceneNumber == -1 && _vm->getGameType() == GType_IHNM) {
		// Return back to the character selection screen in IHNM
		_vm->_scene->changeScene(154, entrance, kTransitionFade, 8);
	} else {
		_vm->_scene->changeScene(sceneNumber, entrance, (sceneNumber == ITE_SCENE_ENDCREDIT1) ? kTransitionFade : kTransitionNoFade);
	}

	if (_vm->_interface->getMode() == kPanelPlacard ||
		_vm->_interface->getMode() == kPanelCutaway ||
		_vm->_interface->getMode() == kPanelVideo) {
		_vm->_gfx->showCursor(true);
		_vm->_interface->setMode(kPanelMain);
	}

	_pendingVerb = _vm->_script->getVerbType(kVerbNone);
	_currentObject[0] = _currentObject[1] = ID_NOTHING;
	showVerb();	// calls setStatusText("")

	if (_vm->getGameType() == GType_IHNM) {
		// There are some cutaways which are not removed by game scripts, like the cutaway
		// after the intro of IHNM or the cutaway at the end of Ellen's part in the IHNM demo.
		// Clear any remaining cutaways here
		_vm->_anim->clearCutaway();
		_vm->_gfx->setCursor(kCursorNormal);
	}
}

// Script function #17 (0x11)
// Param1: object id
// Param2: sprite index
void Script::sfSetObjImage(SCRIPTFUNC_PARAMS) {
	uint16 objectId = thread->pop();
	uint16 spriteId = thread->pop();
	ObjectData *obj = _vm->_actor->getObj(objectId);

	if (_vm->getGameType() == GType_IHNM)
		obj->_spriteListResourceId = spriteId;
	else
		obj->_spriteListResourceId = OBJ_SPRITE_BASE + spriteId;
	_vm->_interface->refreshInventory();
}

// Script function #18 (0x12)
// Param1: object id
// Param2: name index
void Script::sfSetObjName(SCRIPTFUNC_PARAMS) {
	uint16 objectId = thread->pop();
	uint16 nameIdx = thread->pop();
	ObjectData *obj = _vm->_actor->getObj(objectId);

	obj->_nameIndex = nameIdx;
}

// Script function #19 (0x13)
// Param1: object id
void Script::sfGetObjImage(SCRIPTFUNC_PARAMS) {
	uint16 objectId = thread->pop();
	ObjectData *obj = _vm->_actor->getObj(objectId);

	if (_vm->getGameType() == GType_IHNM)
		thread->_returnValue = obj->_spriteListResourceId;
	else
		thread->_returnValue = obj->_spriteListResourceId - OBJ_SPRITE_BASE;
}

// Script function #20 (0x14)
void Script::sfGetNumber(SCRIPTFUNC_PARAMS) {
	if (_vm->_interface->_statusTextInputState == kStatusTextInputFirstRun) {
		_vm->_interface->enterStatusString();
		thread->wait(kWaitTypeStatusTextInput);
		disContinue = true;
	} else {
		if (_vm->_interface->_statusTextInputState == kStatusTextInputAborted) {
			thread->_returnValue = -1;
		} else {
			thread->_returnValue = atoi(_vm->_interface->_statusTextInputString);
		}

		_vm->_interface->_statusTextInputState = kStatusTextInputFirstRun;
	}
}

// Script function #21 (0x15)
// Param1: door #
void Script::sfScriptOpenDoor(SCRIPTFUNC_PARAMS) {
	int16 doorNumber = thread->pop();

	if (_vm->_scene->getFlags() & kSceneFlagISO) {
		_vm->_isoMap->setTileDoorState(doorNumber, 1);
	} else {
		_vm->_scene->setDoorState(doorNumber, 0);
	}
}

// Script function #22 (0x16)
// Param1: door #
void Script::sfScriptCloseDoor(SCRIPTFUNC_PARAMS) {
	int16 doorNumber = thread->pop();

	if (_vm->_scene->getFlags() & kSceneFlagISO) {
		_vm->_isoMap->setTileDoorState(doorNumber, 0);
	} else {
		_vm->_scene->setDoorState(doorNumber, 0xff);
	}
}

// Script function #23 (0x17)
void Script::sfSetBgdAnimSpeed(SCRIPTFUNC_PARAMS) {
	int16 animId = thread->pop();
	int16 speed = thread->pop();

	_vm->_anim->setFrameTime(animId, _vm->ticksToMSec(speed));
	debug(1, "sfSetBgdAnimSpeed(%d, %d)", animId, speed);
}

// Script function #24 (0x18)
void Script::SF_cycleColors(SCRIPTFUNC_PARAMS) {
	SF_stub("SF_cycleColors", thread, nArgs);

	error("Please, report this to sev");
}

// Script function #25 (0x19)
// Param1: actor id
void Script::sfDoCenterActor(SCRIPTFUNC_PARAMS) {
	_vm->_actor->_centerActor = _vm->_actor->getActor(thread->pop());
}

// Script function #26 (0x1A) nonblocking
// Starts the specified animation
void Script::sfStartBgdAnimSpeed(SCRIPTFUNC_PARAMS) {
	int16 animId = thread->pop();
	int16 cycles = thread->pop();
	int16 speed = thread->pop();

	_vm->_anim->setCycles(animId, cycles);
	_vm->_anim->setFrameTime(animId, _vm->ticksToMSec(speed));

	if (!_vm->_anim->isPlaying(animId))
		_vm->_anim->play(animId, 0);

	debug(1, "sfStartBgdAnimSpeed(%d, %d, %d)", animId, cycles, speed);
}

// Script function #27 (0x1B) nonblocking
// Param1: actor id
// Param2: actor x
// Param3: actor y
void Script::sfScriptWalkToAsync(SCRIPTFUNC_PARAMS) {
	int16 actorId = thread->pop();
	ActorData *actor = _vm->_actor->getActor(actorId);
	Location actorLocation;
	actorLocation.x = thread->pop();
	actorLocation.y = thread->pop();
	actorLocation.z = actor->_location.z;

	actor->_flags &= ~kFollower;
	_vm->_actor->actorWalkTo(actorId, actorLocation);
}

// Script function #28 (0x1C)
void Script::sfEnableZone(SCRIPTFUNC_PARAMS) {
	uint16 objectId = thread->pop();
	int16 flag = thread->pop();
	HitZone *hitZone;

	if (objectTypeId(objectId) == 0)
		return;
	else if (objectTypeId(objectId) == kGameObjectHitZone)
		hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(objectId));
	else
		hitZone = _vm->_scene->_actionMap->getHitZone(objectIdToIndex(objectId));

	if (hitZone == NULL)
		return;

	if (flag) {
		hitZone->setFlag(kHitZoneEnabled);
	} else {
		hitZone->clearFlag(kHitZoneEnabled);
		_vm->_actor->_protagonist->_lastZone = NULL;
	}
}

// Script function #29 (0x1D)
// Param1: actor id
// Param2: current action
void Script::sfSetActorState(SCRIPTFUNC_PARAMS) {
	ActorData *actor = _vm->_actor->getActor(thread->pop());
	int currentAction = thread->pop();

	if ((currentAction >= kActionWalkToPoint) && (currentAction <= kActionWalkToPoint)) {
		wakeUpActorThread(kWaitTypeWalk, actor);
	}
	actor->_currentAction = currentAction;
	actor->_actorFlags &= ~kActorBackwards;
}

// Script function #30 (0x1E) nonblocking
// Param1: actor id
// Param2: actor pos x
// Param3: actor pos y
void Script::sfScriptMoveTo(SCRIPTFUNC_PARAMS) {
	int16 objectId = thread->pop();
	Location location;
	location.x = thread->pop();
	location.y = thread->pop();
	ActorData *actor;
	ObjectData *obj;

	if (_vm->_actor->validActorId(objectId)) {
		actor = _vm->_actor->getActor(objectId);

		actor->_location.x = location.x;
		actor->_location.y = location.y;
	} else {
		if (_vm->_actor->validObjId(objectId)) {
			obj = _vm->_actor->getObj(objectId);
			obj->_location.x = location.x;
			obj->_location.y = location.y;
		}
	}
}

// Script function #31 (0x21)
// Param1: sceneNumber
void Script::sfSceneEq(SCRIPTFUNC_PARAMS) {
	int16 sceneNumber = thread->pop();

	if (_vm->_scene->getSceneResourceId(sceneNumber) == _vm->_scene->currentSceneResourceId())
		thread->_returnValue = 1;
	else
		thread->_returnValue = 0;
}

// Script function #32 (0x20)
void Script::sfDropObject(SCRIPTFUNC_PARAMS) {
	uint16 objectId = thread->pop();
	ObjectData *obj = _vm->_actor->getObj(objectId);
	uint16 spriteId = thread->pop();
	obj->_location.x = thread->pop();
	obj->_location.y = thread->pop();

	if (obj->_sceneNumber == ITE_SCENE_INV) {
		_vm->_interface->removeFromInventory(objectId);
	}

	obj->_sceneNumber = _vm->_scene->currentSceneNumber();

	// HACK for the compact disk in Ellen's chapter
	// Change the scene number of the compact disk so that it's not shown. It will be shown
	// once Ellen says that there's something different (i.e. after speaking with AM)
	// See Actor::actorSpeech for the other part of this hack
	if (_vm->getGameType() == GType_IHNM && _vm->_scene->currentChapterNumber() == 3 &&
		_vm->_scene->currentSceneNumber() == 59 && obj->_id == 16385)
			obj->_sceneNumber = -1;

	if (_vm->getGameType() == GType_IHNM) {
		// Don't update _spriteListResourceId if spriteId is 0 and the object is not the
		// psychic profile. If spriteId == 0, the object's sprite is incorrectly reset.
		// This occurs in the IHNM demo and with some incorrect scripts in the retail version
		// of the game
		if (spriteId > 0 || (spriteId == 0 && objectId == IHNM_OBJ_PROFILE))
			obj->_spriteListResourceId = spriteId;
	} else {
		obj->_spriteListResourceId = OBJ_SPRITE_BASE + spriteId;
	}
}

// Script function #33 (0x21)
void Script::sfFinishBgdAnim(SCRIPTFUNC_PARAMS) {
	int16 animId = thread->pop();

	_vm->_anim->finish(animId);

	debug(1, "sfFinishBgdAnim(%d)", animId);
}

// Script function #34 (0x22)
// Param1: actor id 1
// Param2: actor id 2
void Script::sfSwapActors(SCRIPTFUNC_PARAMS) {
	int16 actorId1 = thread->pop();
	int16 actorId2 = thread->pop();
	ActorData *actor1 = _vm->_actor->getActor(actorId1);
	ActorData *actor2 = _vm->_actor->getActor(actorId2);

	SWAP(actor1->_location, actor2->_location);

	if (actor1->_flags & kProtagonist) {
		actor1->_flags &= ~kProtagonist;
		actor2->_flags |= kProtagonist;
		_vm->_actor->_protagonist = _vm->_actor->_centerActor = actor2;
		if (_vm->getGameType() == GType_IHNM)
			_vm->_scene->setProtag(actorId2);
	} else if (actor2->_flags & kProtagonist) {
		actor2->_flags &= ~kProtagonist;
		actor1->_flags |= kProtagonist;
		_vm->_actor->_protagonist = _vm->_actor->_centerActor = actor1;
		if (_vm->getGameType() == GType_IHNM)
			_vm->_scene->setProtag(actorId1);
	}
}

// Script function #35 (0x23)
// Param1: string rid
// Param2: actorscount
// Param3: actor id1
///....
// Param3: actor idN
void Script::sfSimulSpeech(SCRIPTFUNC_PARAMS) {
	int16 stringId = thread->pop();
	int16 actorsCount = thread->pop();
	int i;
	uint16 actorsIds[ACTOR_SPEECH_ACTORS_MAX];
	const char *string = thread->_strings->getString(stringId);
	int16 sampleResourceId = -1;

	if (actorsCount > ACTOR_SPEECH_ACTORS_MAX)
		error("sfSimulSpeech actorsCount=0x%X exceed ACTOR_SPEECH_ACTORS_MAX", actorsCount);

	for (i = 0; i < actorsCount; i++)
		actorsIds[i] = thread->pop();

	if (thread->_voiceLUT->voices) {
		if (_vm->getGameType() == GType_IHNM && stringId >= 338) {
			sampleResourceId = -1;
		} else {
			sampleResourceId = thread->_voiceLUT->voices[stringId];
			if (sampleResourceId <= 0 || sampleResourceId > 4000)
				sampleResourceId = -1;
		}
	}

	_vm->_actor->simulSpeech(string, actorsIds, actorsCount, 0, sampleResourceId);
	thread->wait(kWaitTypeSpeech);
}

// Script function #36 (0x24) ?
// Param1: actor id
// Param2: actor x
// Param3: actor y
// Param4: actor walk flag
void Script::sfScriptWalk(SCRIPTFUNC_PARAMS) {
	int16 actorId = thread->pop();
	ActorData *actor = _vm->_actor->getActor(actorId);
	Location actorLocation;
	actorLocation.x = thread->pop();
	actorLocation.y = thread->pop();
	actorLocation.z = actor->_location.z;
	uint16 walkFlags = thread->pop();

	actor->_flags &= ~kFollower;
	_vm->_actor->realLocation(actorLocation, ID_NOTHING, walkFlags);

	if (_vm->_actor->actorWalkTo(actorId, actorLocation) && !(walkFlags & kWalkAsync)) {
		thread->waitWalk(actor);
	}

	if (walkFlags & kWalkBackPedal) {
		actor->_actorFlags |= kActorBackwards;
	}

	actor->_actorFlags = (actor->_actorFlags & ~kActorFacingMask) | (walkFlags & kActorFacingMask);
}

// Script function #37 (0x25) nonblocking
// Param1: actor id
// Param2: flags telling how to cycle the frames
// Param3: cycle frame number
// Param4: cycle delay
void Script::sfCycleFrames(SCRIPTFUNC_PARAMS) {
	ActorData *actor = _vm->_actor->getActor(thread->pop());
	int16 flags = thread->pop();
	int cycleFrameSequence = thread->pop();
	int cycleDelay = thread->pop();

	if (flags & kCyclePong) {
		actor->_currentAction = kActionPongFrames;
	} else {
		actor->_currentAction = kActionCycleFrames;
	}

	actor->_actorFlags &= ~(kActorContinuous | kActorRandom | kActorBackwards);

	if (!(flags & kCycleOnce)) {
		actor->_actorFlags |= kActorContinuous;
	}
	if (flags & kCycleRandom) {
		actor->_actorFlags |= kActorRandom;
	}
	if (flags & kCycleReverse) {
		if (_vm->getGameType() == GType_IHNM &&
			_vm->_scene->currentChapterNumber() == 2 && _vm->_scene->currentSceneNumber() == 41) {
			// WORKAROUND: Prevent Benny from walking backwards after talking to the child via the monitor. This
			// occurs in the original as well, and is fixed by not setting the kActorBackwards flag at this point
		} else {
			actor->_actorFlags |= kActorBackwards;
		}
	}

	actor->_cycleFrameSequence = cycleFrameSequence;
	actor->_cycleTimeCount = 0;
	actor->_cycleDelay = cycleDelay;
	actor->_actionCycle = 0;
}

// Script function #38 (0x26) nonblocking
// Param1: actor id
// Param2: frame type
// Param3: frame offset
void Script::sfSetFrame(SCRIPTFUNC_PARAMS) {
	int16 actorId = thread->pop();
	ActorData *actor = _vm->_actor->getActor(actorId);
	int frameType = thread->pop();
	int frameOffset = thread->pop();
	ActorFrameRange *frameRange = _vm->_actor->getActorFrameRange(actorId, frameType);

	actor->_frameNumber = frameRange->frameIndex + frameOffset;

	if (actor->_currentAction != kActionFall) {
		actor->_currentAction = kActionFreeze;
	}
}

// Script function #39 (0x27)
// Sets the right-hand portrait
void Script::sfSetPortrait(SCRIPTFUNC_PARAMS) {
	_vm->_interface->setRightPortrait(thread->pop());
}

// Script function #40 (0x28)
// Sets the left-hand portrait
void Script::sfSetProtagPortrait(SCRIPTFUNC_PARAMS) {
	_vm->_interface->setLeftPortrait(thread->pop());
}

// Script function #41 (0x29) nonblocking
// Links the specified animations for playback

// Param1: ?
// Param2: total linked frame count
// Param3: animation id link target
// Param4: animation id link source
void Script::sfChainBgdAnim(SCRIPTFUNC_PARAMS) {
	int16 animId1 = thread->pop();
	int16 animId = thread->pop();
	int16 cycles = thread->pop();
	int16 speed = thread->pop();

	if (speed >= 0) {
		_vm->_anim->setCycles(animId, cycles);
		_vm->_anim->stop(animId);
		_vm->_anim->setFrameTime(animId, _vm->ticksToMSec(speed));
	}

	_vm->_anim->link(animId1, animId);
	debug(1, "sfChainBgdAnim(%d, %d, %d, %d)", animId1, animId, cycles, speed);
}

// Script function #42 (0x2A)
// Param1: actor id
// Param2: actor x
// Param3: actor y
// Param4: frame seq
void Script::sfScriptSpecialWalk(SCRIPTFUNC_PARAMS) {
	int16 actorId = thread->pop();
	ActorData *actor = _vm->_actor->getActor(actorId);
	Location actorLocation;
	actorLocation.x = thread->pop();
	actorLocation.y = thread->pop();
	actorLocation.z = actor->_location.z;
	int16 walkFrameSequence = thread->pop();

	_vm->_actor->actorWalkTo(actorId, actorLocation);
	actor->_walkFrameSequence = walkFrameSequence;
}

// Script function #43 (0x2B) nonblocking
// Param1: actor id
// Param2: actor x
// Param3: actor y
// Param4: actor direction
// Param5: actor action
// Param6: actor frame number
void Script::sfPlaceActor(SCRIPTFUNC_PARAMS) {
	int16 actorId = thread->pop();
	ActorData *actor = _vm->_actor->getActor(actorId);
	actor->_location.x = thread->pop();
	actor->_location.y = thread->pop();
	actor->_facingDirection = actor->_actionDirection = thread->pop();
	int frameType = thread->pop();
	int frameOffset = thread->pop();
	ActorFrameRange *frameRange;

	debug(1, "sfPlaceActor(id = 0x%x, x=%d, y=%d, dir=%d, frameType=%d, frameOffset=%d)", actorId, actor->_location.x,
		  actor->_location.y, actor->_facingDirection, frameType, frameOffset);

	if (frameType >= 0) {
		frameRange = _vm->_actor->getActorFrameRange(actorId, frameType);
		actor->_frameNumber = frameRange->frameIndex + frameOffset;
		actor->_currentAction = kActionFreeze;
	} else {
		actor->_currentAction = kActionWait;
	}

	actor->_targetObject = ID_NOTHING;
}

// Script function #44 (0x2C) nonblocking
// Checks to see if the user has interrupted a currently playing
// game cinematic. Pushes a zero or positive value if the game
// has not been interrupted.
void Script::sfCheckUserInterrupt(SCRIPTFUNC_PARAMS) {
	thread->_returnValue = (_skipSpeeches == true);
}

// Script function #45 (0x2D)
// Param1: actor id
// Param2: object id
// Param3: actor x
// Param4: actor y
// Param5: actor walk flag
void Script::sfScriptWalkRelative(SCRIPTFUNC_PARAMS) {
	int16 actorId = thread->pop();
	ActorData *actor = _vm->_actor->getActor(actorId);
	int16 objectId = thread->pop();
	Location actorLocation;
	actorLocation.x = thread->pop();
	actorLocation.y = thread->pop();
	actorLocation.z = actor->_location.z;
	uint16 walkFlags = thread->pop();

	actor->_flags &= ~kFollower;
	_vm->_actor->realLocation(actorLocation, objectId, walkFlags);

	if (_vm->_actor->actorWalkTo(actorId, actorLocation) && !(walkFlags & kWalkAsync)) {
		thread->waitWalk(actor);
	}

	if (walkFlags & kWalkBackPedal) {
		actor->_actorFlags |= kActorBackwards;
	}

	actor->_actorFlags = (actor->_actorFlags & ~kActorFacingMask) | (walkFlags & kActorFacingMask);
}

// Script function #46 (0x2E)
// Param1: actor id
// Param2: object id
// Param3: actor x
// Param4: actor y
// Param5: actor walk flag
void Script::sfScriptMoveRelative(SCRIPTFUNC_PARAMS) {
	ActorData *actor = _vm->_actor->getActor(thread->pop());
	int16 objectId = thread->pop();
	Location actorLocation;
	actorLocation.x = thread->pop();
	actorLocation.y = thread->pop();
	actorLocation.z = actor->_location.z;
	uint16 walkFlags = thread->pop();

	_vm->_actor->realLocation(actorLocation, objectId, walkFlags);

	actor->_location = actorLocation;
	actor->_actorFlags = (actor->_actorFlags & ~kActorFacingMask) | (walkFlags & kActorFacingMask);
}

// Script function #47 (0x2F)
void Script::sfSimulSpeech2(SCRIPTFUNC_PARAMS) {
	int16 stringId = thread->pop();
	const char *string = thread->_strings->getString(stringId);
	int16 actorsCount = thread->pop();
	int16 speechFlags = thread->pop();
	int i;
	uint16 actorsIds[ACTOR_SPEECH_ACTORS_MAX];
	int16 sampleResourceId = -1;

	if (actorsCount > ACTOR_SPEECH_ACTORS_MAX)
		error("sfSimulSpeech2 actorsCount=0x%X exceed ACTOR_SPEECH_ACTORS_MAX", actorsCount);

	for (i = 0; i < actorsCount; i++)
		actorsIds[i] = thread->pop();

	if (thread->_voiceLUT->voices) {
		sampleResourceId = thread->_voiceLUT->voices[stringId];
		if (sampleResourceId <= 0 || sampleResourceId > 4000)
			sampleResourceId = -1;
	}

	_vm->_actor->simulSpeech(string, actorsIds, actorsCount, speechFlags, sampleResourceId);
	thread->wait(kWaitTypeSpeech);
}


// Script function #48 (0x30)
// Param1: string rid
void Script::sfPlacard(SCRIPTFUNC_PARAMS) {
	int stringId = thread->pop();
	Surface *backBuffer = _vm->_gfx->getBackBuffer();
	static PalEntry cur_pal[PAL_ENTRIES];
	PalEntry *pal;
	Event event;
	Event *q_event;

	thread->wait(kWaitTypePlacard);

	_vm->_interface->rememberMode();
	_vm->_interface->setMode(kPanelPlacard);

	event.type = kEvTOneshot;
	event.code = kCursorEvent;
	event.op = kEventHide;
	q_event = _vm->_events->queue(&event);

	_vm->_interface->setFadeMode(kFadeOut);

	// Fade to black out
	_vm->_gfx->getCurrentPal(cur_pal);
	event.type = kEvTImmediate;
	event.code = kPalEvent;
	event.op = kEventPalToBlack;
	event.time = 0;
	event.duration = kNormalFadeDuration;
	event.data = cur_pal;
	q_event = _vm->_events->chain(q_event, &event);

	// set fade mode
	event.type = kEvTImmediate;
	event.code = kInterfaceEvent;
	event.op = kEventSetFadeMode;
	event.param = kNoFade;
	event.time = 0;
	event.duration = 0;
	q_event = _vm->_events->chain(q_event, &event);

	event.type = kEvTOneshot;
	event.code = kInterfaceEvent;
	event.op = kEventClearStatus;
	q_event = _vm->_events->chain(q_event, &event);

	event.type = kEvTOneshot;
	event.code = kGraphicsEvent;
	event.op = kEventFillRect;
	event.data = backBuffer;
	event.param = 138;
	event.param2 = 0;
	event.param3 = _vm->_scene->getHeight();
	event.param4 = 0;
	event.param5 = _vm->getDisplayWidth();
	q_event = _vm->_events->chain(q_event, &event);

	// Put the text in the center of the viewport, assuming it will fit on
	// one line. If we cannot make that assumption we'll need to extend
	// the text drawing function so that it can center text around a point.
	// It doesn't end up in exactly the same spot as the original did it,
	// but it's close enough for now at least.

	TextListEntry textEntry;

	textEntry.knownColor = kKnownColorBrightWhite;
	textEntry.effectKnownColor = kKnownColorBlack;
	textEntry.point.x = _vm->getDisplayWidth() / 2;
	textEntry.point.y = (_vm->_scene->getHeight() - _vm->_font->getHeight(kKnownFontMedium)) / 2;
	textEntry.font = kKnownFontMedium;
	textEntry.flags = (FontEffectFlags)(kFontOutline | kFontCentered);
	textEntry.text = thread->_strings->getString(stringId);

	_placardTextEntry = _vm->_scene->_textList.addEntry(textEntry);

	event.type = kEvTOneshot;
	event.code = kTextEvent;
	event.op = kEventDisplay;
	event.data = _placardTextEntry;
	q_event = _vm->_events->chain(q_event, &event);

	_vm->_scene->getBGPal(pal);
	event.type = kEvTImmediate;
	event.code = kPalEvent;
	event.op = kEventBlackToPal;
	event.time = 0;
	event.duration = kNormalFadeDuration;
	event.data = pal;
	q_event = _vm->_events->chain(q_event, &event);

	event.type = kEvTOneshot;
	event.code = kScriptEvent;
	event.op = kEventThreadWake;
	event.param = kWaitTypePlacard;
	q_event = _vm->_events->chain(q_event, &event);

}

// Script function #49 (0x31)
void Script::sfPlacardOff(SCRIPTFUNC_PARAMS) {
	thread->wait(kWaitTypePlacard);

	_vm->_scene->clearPlacard();
}

void Script::sfPsychicProfile(SCRIPTFUNC_PARAMS) {
	thread->wait(kWaitTypePlacard);

	_vm->_scene->showPsychicProfile(thread->_strings->getString(thread->pop()));
}

void Script::sfPsychicProfileOff(SCRIPTFUNC_PARAMS) {
	thread->wait(kWaitTypePlacard);

	// This is called a while after the psychic profile is
	// opened, to close it automatically
	_vm->_scene->clearPsychicProfile();
}

// Script function #50 (0x32)
void Script::sfSetProtagState(SCRIPTFUNC_PARAMS) {
	_vm->_actor->setProtagState(thread->pop());
}

// Script function #51 (0x33)
void Script::sfResumeBgdAnim(SCRIPTFUNC_PARAMS) {
	int16 animId = thread->pop();
	int16 cycles = thread->pop();

	_vm->_anim->resume(animId, cycles);
	debug(1, "sfResumeBgdAnimSpeed(%d, %d)", animId, cycles);

}

// Script function #52 (0x34)
// Param1: actor id
// Param2: x
// Param3: y
// Param4: unknown
// Param5: actionCycle
// Param6: flags
void Script::sfThrowActor(SCRIPTFUNC_PARAMS) {
	ActorData *actor = _vm->_actor->getActor(thread->pop());
	actor->_finalTarget.x = thread->pop();
	actor->_finalTarget.y = thread->pop();
	actor->_finalTarget.z = actor->_location.z;
	thread->pop();	// not used
	int32 actionCycle = thread->pop();
	int16 flags = thread->pop();

	actor->_currentAction = kActionFall;
	actor->_actionCycle = actionCycle;
	actor->_fallAcceleration	= -20;
	actor->_fallVelocity = - (actor->_fallAcceleration * actor->_actionCycle) / 2;
	actor->_fallPosition = actor->_location.z << 4;

	actor->_actionCycle--;
	if (!(flags & kWalkAsync)) {
		thread->waitWalk(actor);
	}
}

// Script function #53 (0x35)
// Param1: actor id
// Param2: target object
void Script::sfWaitWalk(SCRIPTFUNC_PARAMS) {
	ActorData *actor = _vm->_actor->getActor(thread->pop());

	if ((actor->_currentAction == kActionWalkToPoint) ||
		(actor->_currentAction == kActionWalkToLink) ||
		(actor->_currentAction == kActionFall)) {
			thread->waitWalk(actor);
	}
}

// Script function #54 (0x36)
void Script::sfScriptSceneID(SCRIPTFUNC_PARAMS) {
	thread->_returnValue = _vm->_scene->currentSceneNumber();
}

// Script function #55 (0x37)
// Param1: actor id
// Param2: scene number
void Script::sfChangeActorScene(SCRIPTFUNC_PARAMS) {
	ActorData *actor = _vm->_actor->getActor(thread->pop());
	actor->_sceneNumber = thread->pop();
}

// Script function #56 (0x38)
// Param1: actor id
// Param2: z
// Param3: frame seq
// Param4: flags
void Script::sfScriptClimb(SCRIPTFUNC_PARAMS) {
	ActorData *actor = _vm->_actor->getActor(thread->pop());
	actor->_finalTarget.z = thread->pop();
	int cycleFrameSequence = thread->pop();
	uint16 flags = thread->pop();

	actor->_flags &= ~kFollower;
	actor->_actionCycle = 1;
	actor->_cycleFrameSequence = cycleFrameSequence;
	actor->_currentAction = kActionClimb;
	if (!(flags & kWalkAsync)) {
		thread->waitWalk(actor);
	}
}

// Script function #57 (0x39)
// Param1: door #
// Param2: door state
void Script::sfSetDoorState(SCRIPTFUNC_PARAMS) {
	int16 doorNumber = thread->pop();
	int16 doorState = thread->pop();

	if (_vm->_scene->getFlags() & kSceneFlagISO) {
		_vm->_isoMap->setTileDoorState(doorNumber, doorState);
	} else {
		_vm->_scene->setDoorState(doorNumber, doorState);
	}
}

// Script function #58 (0x3A)
// Param1: actor id
// Param2: z
void Script::sfSetActorZ(SCRIPTFUNC_PARAMS) {
	int16 objectId = thread->pop();
	int16 z = thread->pop();
	ActorData *actor;
	ObjectData *obj;

	if (_vm->_actor->validActorId(objectId)) {
		actor = _vm->_actor->getActor(objectId);
		actor->_location.z = z;
	} else {
		if (_vm->_actor->validObjId(objectId)) {
			obj = _vm->_actor->getObj(objectId);
			obj->_location.z = z;
		}
	}
}

// Script function #59 (0x3B)
// Param1: stringId
// Param2: flags
// Param3: color
// Param4: x
// Param5: y
void Script::sfScriptText(SCRIPTFUNC_PARAMS) {
	const char *text = thread->_strings->getString(thread->pop());
	int16 flags = thread->pop();
	int color = thread->pop();
	Point point;
	point.x = thread->pop();
	point.y = thread->pop();
	Rect rect;
	int width = _vm->_font->getStringWidth(kKnownFontScript, text, 0, kFontOutline);

	rect.top = point.y - 6;
	rect.setHeight(12);
	rect.left = point.x - width / 2;
	rect.setWidth(width);

	_vm->_actor->setSpeechColor(color, _vm->KnownColor2ColorId(kKnownColorBlack));
	_vm->_actor->nonActorSpeech(rect, &text, 1, -1, flags);
}

// Script function #60 (0x3C)
// Param1: actor id
void Script::sfGetActorX(SCRIPTFUNC_PARAMS) {
	ActorData *actor = _vm->_actor->getActor(thread->pop());

	thread->_returnValue = actor->_location.x >> 2;
}

// Script function #61 (0x3D)
// Param1: actor id
void Script::sfGetActorY(SCRIPTFUNC_PARAMS) {
	ActorData *actor = _vm->_actor->getActor(thread->pop());

	thread->_returnValue = actor->_location.y >> 2;
}

// Script function #62 (0x3E)
void Script::sfEraseDelta(SCRIPTFUNC_PARAMS) {
	Surface *backGroundSurface = _vm->_render->getBackGroundSurface();
	BGInfo backGroundInfo;

	_vm->_scene->getBGInfo(backGroundInfo);
	backGroundSurface->blit(backGroundInfo.bounds, backGroundInfo.buffer);
}

// Script function #63 (0x3F)
void Script::sfPlayMusic(SCRIPTFUNC_PARAMS) {
	if (_vm->getGameType() == GType_ITE) {
		int16 param = thread->pop() + 9;

		if (param >= 9 && param <= 34) {
			_vm->_music->setVolume(_vm->_musicVolume == 10 ? -1 : _vm->_musicVolume * 25, 1);
			_vm->_music->play(param);
		} else {
			_vm->_music->stop();
		}
	} else {
		int16 param1 = thread->pop();
		int16 param2 = thread->pop();

		if (param1 < 0) {
			_vm->_music->stop();
			return;
		}

		if (param1 >= _vm->_music->_songTableLen) {
			warning("sfPlayMusic: Wrong song number (%d > %d)", param1, _vm->_music->_songTableLen - 1);
		} else {
			_vm->_music->setVolume(_vm->_musicVolume == 10 ? -1 : _vm->_musicVolume * 25, 1);
			_vm->_music->play(_vm->_music->_songTable[param1], param2 ? MUSIC_LOOP : MUSIC_NORMAL);
			if (!_vm->_scene->haveChapterPointsChanged()) {
				_vm->_scene->setCurrentMusicTrack(param1);
				_vm->_scene->setCurrentMusicRepeat(param2);
			} else {
				// Don't save this music track when saving in IHNM
				_vm->_scene->setChapterPointsChanged(false);
			}
		}
	}
}

// Script function #64 (0x40)
void Script::sfPickClimbOutPos(SCRIPTFUNC_PARAMS) {
	int16 u, v, t;
	ActorData *protagonist = _vm->_actor->_protagonist;
	while (true) {

		u = (_vm->_rnd.getRandomNumber(63) & 63) + 40;
		v = (_vm->_rnd.getRandomNumber(63) & 63) + 40;
		t = _vm->_isoMap->getTileIndex(u, v, 6);
		if (t == 65) {
			protagonist->_location.u() = (u << 4) + 4;
			protagonist->_location.v() = (v << 4) + 4;
			protagonist->_location.z = 48;
			break;
		}

	}
}

// Script function #65 (0x41)
void Script::sfTossRif(SCRIPTFUNC_PARAMS) {
	uint16 direction;
	ActorData *protagonist = _vm->_actor->_protagonist;
	int16 uc = protagonist->_location.u() >> 4;
	int16 vc = protagonist->_location.v() >> 4;

	if (_vm->_isoMap->findNearestChasm(uc, vc, direction)) {
		uc <<= 4;
		vc <<= 4;
		protagonist->_facingDirection = direction;

		protagonist->_finalTarget.u() = uc;
		protagonist->_finalTarget.v() = vc;
		protagonist->_finalTarget.z = -40;
		protagonist->_currentAction = kActionFall;
		protagonist->_actionCycle = 24;
		protagonist->_fallAcceleration = - 20;
		protagonist->_fallVelocity = - (protagonist->_fallAcceleration * 16) / 2 - (44 / 12);
		protagonist->_fallPosition = protagonist->_location.z << 4;
		protagonist->_actionCycle--;
	}
}

// Script function #66 (0x42)
void Script::sfShowControls(SCRIPTFUNC_PARAMS) {
	// It has zero implementation in Win rerelase, and in DOS
	// release it deals with video ports.
}

// Script function #67 (0x43)
void Script::sfShowMap(SCRIPTFUNC_PARAMS) {
	_vm->_interface->setMode(kPanelMap);
}

// Script function #68 (0x44)
void Script::sfPuzzleWon(SCRIPTFUNC_PARAMS) {
	thread->_returnValue = _vm->_puzzle->isSolved();
}

// Script function #69 (0x45)
void Script::sfEnableEscape(SCRIPTFUNC_PARAMS) {
	if (thread->pop())
		_abortEnabled = true;
	else {
		_skipSpeeches = false;
		_abortEnabled = false;
	}
}

// Script function #70 (0x46)
void Script::sfPlaySound(SCRIPTFUNC_PARAMS) {
	int16 param = thread->pop();
	int res;

	if (param >= 0 && param < _vm->_sndRes->_fxTableLen) {
		res = _vm->_sndRes->_fxTable[param].res;
		if (_vm->getFeatures() & GF_CD_FX)
			res -= 14;
		_vm->_sndRes->playSound(res, _vm->_sndRes->_fxTable[param].vol, false);
	} else {
		_vm->_sound->stopSound();
	}
}

// Script function #71 (0x47)
void Script::sfPlayLoopedSound(SCRIPTFUNC_PARAMS) {
	int16 param = thread->pop();
	int res;

	if (param >= 0 && param < _vm->_sndRes->_fxTableLen) {
		res = _vm->_sndRes->_fxTable[param].res;
		if (_vm->getFeatures() & GF_CD_FX)
			res -= 14;

		_vm->_sndRes->playSound(res, _vm->_sndRes->_fxTable[param].vol, true);
	} else {
		_vm->_sound->stopSound();
	}
}

// Script function #72 (0x48)
// Param1: animation id
void Script::sfGetDeltaFrame(SCRIPTFUNC_PARAMS) {
	thread->_returnValue = _vm->_anim->getCurrentFrame((uint16)thread->pop());
}

// Script function #73 (0x49)
void Script::sfShowProtect(SCRIPTFUNC_PARAMS) {
	if (_vm->_copyProtection) {
		thread->wait(kWaitTypeRequest);

		_vm->_interface->setMode(kPanelProtect);
	}
}

// Script function #74 (0x4A)
void Script::sfProtectResult(SCRIPTFUNC_PARAMS) {
	if (_vm->_copyProtection) {
		thread->_returnValue = _vm->_interface->getProtectHash();
	} else {
		//cheating
		int protectHash = thread->pop();
		thread->push(protectHash);
		thread->_returnValue = protectHash;
	}
}

// Script function #75 (0x4b)
void Script::sfRand(SCRIPTFUNC_PARAMS) {
	thread->_returnValue = _vm->_rnd.getRandomNumber(thread->pop() - 1);
}

// Script function #76 (0x4c)
void Script::sfFadeMusic(SCRIPTFUNC_PARAMS) {
	_vm->_music->setVolume(0, 1000);
}

// Script function #77 (0x4d)
void Script::sfPlayVoice(SCRIPTFUNC_PARAMS) {
	int16 param = thread->pop();

	if (param > 0) {
		_vm->_sndRes->playVoice(param + 3712);
	} else {
		_vm->_sound->stopSound();
	}
}

void Script::finishDialog(int strID, int replyID, int flags, int bitOffset) {
	byte *addr;
	const char *str;

	if (_conversingThread) {
		_vm->_interface->setMode(kPanelNull);

		if (_vm->getGameType() == GType_IHNM) {
			str = _conversingThread->_strings->getString(strID);
			if (*str != '[') {
				int sampleResourceId = -1;
				sampleResourceId = _conversingThread->_voiceLUT->voices[strID];
				if (sampleResourceId < 0 || sampleResourceId > 4000)
					sampleResourceId = -1;

				_vm->_actor->actorSpeech(_vm->_actor->_protagonist->_id, &str, 1, sampleResourceId, 0);
			}
		}

		_conversingThread->_flags &= ~kTFlagWaiting;

		_conversingThread->push(replyID);

		if (flags & kReplyOnce) {
			addr = _conversingThread->_staticBase + (bitOffset >> 3);
			*addr |= (1 << (bitOffset & 7));
		}
	}

	_conversingThread = NULL;
	wakeUpThreads(kWaitTypeDialogBegin);
}

void Script::sfSetChapterPoints(SCRIPTFUNC_PARAMS) {
	int chapter = _vm->_scene->currentChapterNumber();
	_vm->_ethicsPoints[chapter] = thread->pop();
	int16 barometer = thread->pop();
	static PalEntry cur_pal[PAL_ENTRIES];

	_vm->_spiritualBarometer = _vm->_ethicsPoints[chapter] * 256 / barometer;
	_vm->_scene->setChapterPointsChanged(true);		// don't save this music when saving in IHNM

	if (_vm->_spiritualBarometer > 255)
		_vm->_gfx->setPaletteColor(kIHNMColorPortrait, 0xff, 0xff, 0xff);
	else
		_vm->_gfx->setPaletteColor(kIHNMColorPortrait,
			_vm->_spiritualBarometer * _vm->_interface->_portraitBgColor.red / 256,
			_vm->_spiritualBarometer * _vm->_interface->_portraitBgColor.green / 256,
			_vm->_spiritualBarometer * _vm->_interface->_portraitBgColor.blue / 256);

	_vm->_gfx->getCurrentPal(cur_pal);
	_vm->_gfx->setPalette(cur_pal);
}

void Script::sfSetPortraitBgColor(SCRIPTFUNC_PARAMS) {
	int16 red = thread->pop();
	int16 green = thread->pop();
	int16 blue = thread->pop();

	_vm->_interface->setPortraitBgColor(red, green, blue);
}

void Script::sfScriptStartCutAway(SCRIPTFUNC_PARAMS) {
	int16 cut = thread->pop();
	thread->pop();		// Not used
	int16 fade = thread->pop();

	_vm->_anim->setCutAwayMode(kPanelCutaway);
	_vm->_anim->playCutaway(cut, fade != 0);
}

void Script::sfReturnFromCutAway(SCRIPTFUNC_PARAMS) {
	_vm->_anim->returnFromCutaway();
	thread->wait(kWaitTypeWakeUp);
}

void Script::sfEndCutAway(SCRIPTFUNC_PARAMS) {
	_vm->_anim->endCutaway();
}

void Script::sfGetMouseClicks(SCRIPTFUNC_PARAMS) {
	thread->_returnValue = _vm->getMouseClickCount();
}

void Script::sfResetMouseClicks(SCRIPTFUNC_PARAMS) {
	_vm->resetMouseClickCount();
}

// Used in IHNM only
// Param1: frames
void Script::sfWaitFrames(SCRIPTFUNC_PARAMS) {
	int16 frames = thread->pop();

	if (!_skipSpeeches)
		thread->waitFrames(_vm->_frameCount + frames);
}

void Script::sfScriptFade(SCRIPTFUNC_PARAMS) {
	int16 firstPalEntry = thread->pop();
	int16 lastPalEntry = thread->pop();
	int16 startingBrightness = thread->pop();
	int16 endingBrightness = thread->pop();
	Event event;
	static PalEntry cur_pal[PAL_ENTRIES];

	_vm->_gfx->getCurrentPal(cur_pal);
	event.type = kEvTImmediate;
	event.code = kPalEvent;
	event.op = kEventPalFade;
	event.time = 0;
	event.duration = kNormalFadeDuration;
	event.data = cur_pal;
	event.param = startingBrightness;
	event.param2 = endingBrightness;
	event.param3 = firstPalEntry;
	event.param4 = lastPalEntry - firstPalEntry + 1;
	_vm->_events->queue(&event);
}

void Script::sfScriptStartVideo(SCRIPTFUNC_PARAMS) {
	int16 vid = thread->pop();
	int16 fade = thread->pop();

	_vm->_anim->setCutAwayMode(kPanelVideo);
	_vm->_anim->startVideo(vid, fade != 0);
}

void Script::sfScriptReturnFromVideo(SCRIPTFUNC_PARAMS) {
	_vm->_anim->returnFromVideo();
}

void Script::sfScriptEndVideo(SCRIPTFUNC_PARAMS) {
	_vm->_anim->endVideo();
}

void Script::sfShowIHNMDemoHelpBg(SCRIPTFUNC_PARAMS) {
	_ihnmDemoCurrentY = 0;
	_vm->_scene->_textList.clear();
	_vm->_interface->setMode(kPanelConverse);
	_vm->_scene->showPsychicProfile(NULL);
}

void Script::sfAddIHNMDemoHelpTextLine(SCRIPTFUNC_PARAMS) {
	int stringId = thread->pop();
	TextListEntry textEntry;
	Event event;

	textEntry.knownColor = kKnownColorBlack;
	textEntry.useRect = true;
	textEntry.rect.left = 245;
	textEntry.rect.setHeight(210 + 76);
	textEntry.rect.setWidth(226);
	textEntry.rect.top = 76 + _ihnmDemoCurrentY;
	textEntry.font = kKnownFontVerb;
	textEntry.flags = (FontEffectFlags)(kFontCentered);
	textEntry.text = thread->_strings->getString(stringId);

	TextListEntry *_psychicProfileTextEntry = _vm->_scene->_textList.addEntry(textEntry);

	event.type = kEvTOneshot;
	event.code = kTextEvent;
	event.op = kEventDisplay;
	event.data = _psychicProfileTextEntry;
	_vm->_events->queue(&event);

	_ihnmDemoCurrentY += _vm->_font->getHeight(kKnownFontVerb, thread->_strings->getString(stringId), 226, kFontCentered);
}

void Script::sfShowIHNMDemoHelpPage(SCRIPTFUNC_PARAMS) {
	// Note: The IHNM demo changes panel mode to 8 (kPanelProtect in ITE)
	// when changing pages
	_vm->_interface->setMode(kPanelPlacard);
	_ihnmDemoCurrentY = 0;
}

void Script::sfVstopFX(SCRIPTFUNC_PARAMS) {
	_vm->_sound->stopSound();
}

void Script::sfVstopLoopedFX(SCRIPTFUNC_PARAMS) {
	_vm->_sound->stopSound();
}

void Script::sfDemoSetInteractive(SCRIPTFUNC_PARAMS) {
	if (thread->pop() == 0) {
		_vm->_interface->deactivate();
		_vm->_interface->setMode(kPanelNull);
	}

	// Note: the original also sets an appropriate flag here, but we don't,
	// as we don't use it
}

void Script::sfDemoIsInteractive(SCRIPTFUNC_PARAMS) {
	thread->_returnValue = 0;
}

void Script::sfVsetTrack(SCRIPTFUNC_PARAMS) {
	int16 chapter = thread->pop();
	int16 sceneNumber = thread->pop();
	int16 actorsEntrance = thread->pop();

	debug(2, "sfVsetTrrack(%d, %d, %d)", chapter, sceneNumber, actorsEntrance);

	_vm->_scene->changeScene(sceneNumber, actorsEntrance, kTransitionFade, chapter);
}

void Script::sfGetPoints(SCRIPTFUNC_PARAMS) {
	int16 index = thread->pop();

	if (index >= 0 && index < ARRAYSIZE(_vm->_ethicsPoints))
		thread->_returnValue = _vm->_ethicsPoints[index];
	else
		thread->_returnValue = 0;
}

void Script::sfSetGlobalFlag(SCRIPTFUNC_PARAMS) {
	int16 flag = thread->pop();

	if (flag >= 0 && flag < 32)
		_vm->_globalFlags |= (1 << flag);
}

void Script::sfClearGlobalFlag(SCRIPTFUNC_PARAMS) {
	int16 flag = thread->pop();

	if (flag >= 0 && flag < 32)
		_vm->_globalFlags &= ~(1 << flag);
}

void Script::sfTestGlobalFlag(SCRIPTFUNC_PARAMS) {
	int16 flag = thread->pop();

	if (flag >= 0 && flag < 32 && _vm->_globalFlags & (1 << flag))
		thread->_returnValue = 1;
	else
		thread->_returnValue = 0;
}

void Script::sfSetPoints(SCRIPTFUNC_PARAMS) {
	int16 index = thread->pop();
	int16 points = thread->pop();

	if (index >= 0 && index < ARRAYSIZE(_vm->_ethicsPoints))
		_vm->_ethicsPoints[index] = points;
}

void Script::sfSetSpeechBox(SCRIPTFUNC_PARAMS) {
	int16 param1 = thread->pop();
	int16 param2 = thread->pop();
	int16 param3 = thread->pop();
	int16 param4 = thread->pop();

	_vm->_actor->_speechBoxScript.left = param1;
	_vm->_actor->_speechBoxScript.top = param2;
	_vm->_actor->_speechBoxScript.setWidth(param3 - param1);
	_vm->_actor->_speechBoxScript.setHeight(param4 - param2);
}

void Script::sfDebugShowData(SCRIPTFUNC_PARAMS) {
	int16 param = thread->pop();
	char buf[50];

	snprintf(buf, 50, "Reached breakpoint %d", param);

	_vm->_interface->setStatusText(buf);
}

void Script::sfWaitFramesEsc(SCRIPTFUNC_PARAMS) {
	thread->_returnValue = _vm->_framesEsc;
}

void Script::sfQueueMusic(SCRIPTFUNC_PARAMS) {
	int16 param1 = thread->pop();
	int16 param2 = thread->pop();
	Event event;

	if (param1 < 0) {
		_vm->_music->stop();
		return;
	}

	if (param1 >= _vm->_music->_songTableLen) {
		warning("sfQueueMusic: Wrong song number (%d > %d)", param1, _vm->_music->_songTableLen - 1);
	} else {
		_vm->_music->setVolume(_vm->_musicVolume == 10 ? -1 : _vm->_musicVolume * 25, 1);
		event.type = kEvTOneshot;
		event.code = kMusicEvent;
		event.param = _vm->_music->_songTable[param1];
		event.param2 = param2 ? MUSIC_LOOP : MUSIC_NORMAL;
		event.op = kEventPlay;
		event.time = _vm->ticksToMSec(500);		// I find the delay in the original to be too long, so I've set it to
												// wait for half the time, which sounds better when chapter points
												// change
												// FIXME: If this is too short for other cases apart from chapter
												// point change, set it back to 1000
		_vm->_events->queue(&event);

		if (!_vm->_scene->haveChapterPointsChanged()) {
			_vm->_scene->setCurrentMusicTrack(param1);
			_vm->_scene->setCurrentMusicRepeat(param2);
		} else {
			// Don't save this music track when saving in IHNM
			_vm->_scene->setChapterPointsChanged(false);
		}
	}
}

void Script::sfDisableAbortSpeeches(SCRIPTFUNC_PARAMS) {
	_vm->_interface->disableAbortSpeeches(thread->pop() != 0);
}

void Script::sfNull(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();
}

void Script::SF_stub(const char *name, ScriptThread *thread, int nArgs) {
	char buf[256], buf1[100];

	snprintf(buf, 256, "STUB: %s(", name);

	for (int i = 0; i < nArgs; i++) {
		snprintf(buf1, 100, "%d", thread->pop());
		strncat(buf, buf1, 256);
		if (i + 1 < nArgs)
			strncat(buf, ", ", 256);
	}

	debug(0, "%s)", buf);
}

} // End of namespace Saga
