/* ScummVM - Scumm Interpreter
 * Copyright (C) 2004-2005 The ScummVM project
 *
 * The ReInherit Engine is (C)2000-2003 by Daniel Balsom.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header$
 *
 */

// 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/objectdata.h"
#include "saga/render.h"
#include "saga/sound.h"
#include "saga/sndres.h"

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

#include "saga/scene.h"

namespace Saga {

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

void Script::setupScriptFuncList(void) {
	static const ScriptFunctionDescription scriptFunctionsList[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(SF_gotoScene),
		OPCODE(SF_setObjImage),
		OPCODE(SF_setObjName),
		OPCODE(SF_getObjImage),
		OPCODE(SF_getNumber),
		OPCODE(sfScriptOpenDoor),
		OPCODE(sfScriptCloseDoor),
		OPCODE(sfSetBgdAnimSpeed),
		OPCODE(SF_cycleColors),
		OPCODE(sfDoCenterActor),
		OPCODE(sfStartBgdAnimSpeed),
		OPCODE(sfScriptWalkToAsync),
		OPCODE(SF_enableZone),
		OPCODE(sfSetActorState),
		OPCODE(scriptMoveTo),
		OPCODE(SF_sceneEq),
		OPCODE(SF_dropObject),
		OPCODE(sfFinishBgdAnim),
		OPCODE(sfSwapActors),
		OPCODE(sfSimulSpeech),
		OPCODE(sfScriptWalk),
		OPCODE(sfCycleFrames),
		OPCODE(sfSetFrame),
		OPCODE(sfSetPortrait),
		OPCODE(sfSetProtagPortrait),
		OPCODE(sfChainBgdAnim),
		OPCODE(SF_scriptSpecialWalk),
		OPCODE(sfPlaceActor),
		OPCODE(SF_checkUserInterrupt),
		OPCODE(SF_walkRelative),
		OPCODE(SF_moveRelative),
		OPCODE(SF_simulSpeech2),
		OPCODE(sfPlacard),
		OPCODE(sfPlacardOff),
		OPCODE(SF_setProtagState),
		OPCODE(sfResumeBgdAnim),
		OPCODE(SF_throwActor),
		OPCODE(SF_waitWalk),
		OPCODE(SF_sceneID),
		OPCODE(SF_changeActorScene),
		OPCODE(SF_climb),
		OPCODE(sfSetDoorState),
		OPCODE(SF_setActorZ),
		OPCODE(SF_text),
		OPCODE(SF_getActorX),
		OPCODE(SF_getActorY),
		OPCODE(SF_eraseDelta),
		OPCODE(sfPlayMusic),
		OPCODE(SF_pickClimbOutPos),
		OPCODE(SF_tossRif),
		OPCODE(SF_showControls),
		OPCODE(SF_showMap),
		OPCODE(SF_puzzleWon),
		OPCODE(sfEnableEscape),
		OPCODE(sfPlaySound),
		OPCODE(SF_playLoopedSound),
		OPCODE(SF_getDeltaFrame),
		OPCODE(SF_showProtect),
		OPCODE(SF_protectResult),
		OPCODE(sfRand),
		OPCODE(SF_fadeMusic),
		OPCODE(SF_playVoice)
	};
	_scriptFunctionsList = scriptFunctionsList;
}

// Script function #0 (0x00)
// Print a debugging message
void Script::sfPutString(SCRIPTFUNC_PARAMS) {
	const char *str;
	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;
	time = thread->pop();

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

// Script function #2 (0x02)
void Script::sfTakeObject(SCRIPTFUNC_PARAMS) {
	uint16 objectId = thread->pop();
	ObjectData *obj;
	obj = _vm->_actor->getObj(objectId);
	if (obj->sceneNumber != ITE_SCENE_INV) {
		obj->sceneNumber = ITE_SCENE_INV;
		//_vm->_interface->addToInventory(index); TODO: do it
	}
}

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

	thread->_returnValue = (obj->sceneNumber == ITE_SCENE_INV) ? 1 : 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) {
	int16 stringIndex = thread->pop();

	_vm->_interface->setStatusText(thread->_strings->getString(stringIndex));
}

// Script function #5 (0x05)
void Script::sfMainMode(SCRIPTFUNC_PARAMS) {
	_vm->_actor->_centerActor = _vm->_actor->_protagonist;
	showVerb();		
	_vm->_interface->activate();
	_vm->_interface->setMode(kPanelInventory);
	setPointerVerb();
}

// Script function #6 (0x06) blocking
// Param1: actor id
// Param2: actor x
// Param3: actor y
void Script::sfScriptWalkTo(SCRIPTFUNC_PARAMS) {
	uint16 actorId;
	Location actorLocation;
	ActorData *actor;

	actorId = thread->pop();
	actorLocation.x = thread->pop();
	actorLocation.y = thread->pop();
	
	actor = _vm->_actor->getActor(actorId);
	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;
	uint16 action;
	uint16 theObject;
	uint16 withObject;
	int16 scriptEntryPointNumber;
	int16 moduleNumber;
	ActorData *actor;
	ObjectData *obj;
	const HitZone *hitZone;
	EVENT event;

	objectId = thread->pop();
	action = thread->pop();
	theObject = thread->pop();
	withObject = thread->pop();

	switch (objectTypeId(objectId)) {
		case kGameObjectObject:
			obj = _vm->_actor->getObj(objectId);
			scriptEntryPointNumber = obj->scriptEntrypointNumber;
			if (scriptEntryPointNumber <= 0) {
				return;
			}
			moduleNumber = 0; 
			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();
			}
			break;
		case kGameObjectHitZone:
		case kGameObjectStepZone:
			if (objectTypeId(objectId) == kGameObjectHitZone) {
				hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(objectId));
			} else {
				hitZone = _vm->_scene->_actionMap->getHitZone(objectIdToIndex(objectId));
			}
			scriptEntryPointNumber = hitZone->getScriptNumber();
			moduleNumber = _vm->_scene->getScriptModuleNumber();			
			break;
		default:
			error("Script::sfScriptDoAction wrong object type");
	}

	event.type = ONESHOT_EVENT;
	event.code = SCRIPT_EVENT;
	event.op = EVENT_EXEC_NONBLOCKING;
	event.time = 0;
	event.param = moduleNumber;
	event.param2 = scriptEntryPointNumber;
	event.param3 = action;		// Action
	event.param4 = theObject;	// Object
	event.param5 = theObject;	// 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) {
	int16 actorId;
	int actorDirection;
	ActorData *actor;

	actorId = thread->pop();
	actorDirection =  thread->pop();

	actor = _vm->_actor->getActor(actorId);
	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->play(animId, kRepeatSpeed);

	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) {
	int16 lock;

	lock = thread->pop();

	if (lock) {
		_vm->_interface->deactivate();
	} else {
		_vm->_interface->activate();
	}

}

// Script function #12 (0x0C)
// Disables mouse input, etc.
void Script::sfPreDialog(SCRIPTFUNC_PARAMS) {
	_vm->_interface->deactivate();
	; // clear converse text
	if (_vm->_interface->isInMainMode())
		_vm->_interface->setMode(kPanelConverse);
	else
		; // display zero text
	_vm->_interface->setMode(kPanelNull);

	debug(1, "stub: SF_preDialog()");
}

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

	actorId = thread->pop();


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

// Script function #14 (0x0E)
// Param1: actor id
// Param2: object id
void Script::sfFaceTowards(SCRIPTFUNC_PARAMS) {
	int16 actorId;
	int16 targetObject;
	ActorData *actor;

	actorId = thread->pop();
	targetObject = thread->pop();

	actor = _vm->_actor->getActor(actorId);
	actor->targetObject = targetObject;
}

// Script function #15 (0x0F)
// Param1: actor id
// Param2: target object
void Script::sfSetFollower(SCRIPTFUNC_PARAMS) {
	int16 actorId;
	int16 targetObject;

	ActorData *actor;

	actorId = thread->pop();
	targetObject = thread->pop();

	debug(1, "sfSetFollower(%d, %d) [%d]", actorId, targetObject, _vm->_actor->actorIdToIndex(actorId));
	
	actor = _vm->_actor->getActor(actorId);
	actor->targetObject = targetObject;
	if (targetObject != ID_NOTHING) {
		actor->flags |= kFollower;
		actor->actorFlags &= ~kActorNoFollow;
	} else {
		actor->flags &= ~kFollower;
	}
}

static struct SceneSubstitutes {
    int sceneId;
    const char *message;
    const char *name;
    const char *image;
} sceneSubstitutes[] = {
    { 
		7,
		"Tycho says he knows much about the northern lands. Can Rif convince "
		"the Dog to share this knowledge?",
		"The Home of Tycho Northpaw",
		"tycho.bbm"
	},

    {
		27,
		"The scene of the crime may hold many clues, but will the servants of "
		"the Sanctuary trust Rif?",
		"The Sanctuary of the Orb",
		"sanctuar.bbm"
	},

    {
		5,
		"The Rats hold many secrets that could guide Rif on his quest -- assuming "
		"he can get past the doorkeeper.",
		"The Rat Complex",
		"ratdoor.bbm"
	},

    {
		2,
		"The Ferrets enjoy making things and have the materials to do so. How can "
		"that help Rif?",
		"The Ferret Village",
		"ferrets.bbm"
	},

    {
		67,
		"What aid can the noble King of the Elks provide to Rif and his companions?",
		"The Realm of the Forest King",
		"elkenter.bbm"
	},

    {
		3,
		"The King holds Rif's sweetheart hostage. Will the Boar provide any "
		"assistance to Rif?",
		"The Great Hall of the Boar King",
		"boarhall.bbm"
	}
};

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

	for (int i = 0; i < ARRAYSIZE(sceneSubstitutes); i++)
		if (sceneSubstitutes[i].sceneId == sceneNum)
			debug(0, "Scene %d substitute exists", sceneNum);

	debug(1, "stub: SF_gotoScene(%d, %d)", sceneNum, entrance);
}

// Script function #17 (0x11)
void Script::SF_setObjImage(SCRIPTFUNC_PARAMS) {
	error("Not implemented");
/*	int16 obj_param = getSWord(thread->pop());
	int16 sprite_param = getSWord(thread->pop());


	int index = obj_param & 0x1FFF;

	if (index >= ARRAYSIZE(ObjectTable)) {
		return FAILURE;
	}

	ObjectTable[index].spritelistRn = sprite_param + 9;
	_vm->_interface->draw();
*/
}

// Script function #18 (0x12)
void Script::SF_setObjName(SCRIPTFUNC_PARAMS) {
	error("Not implemented");

/*	int obj_param = getSWord(thread->pop());
	int name_param = getSWord(thread->pop());

	int index = obj_param & 0x1FFF;

	if (index >= ARRAYSIZE(ObjectTable)) {
		return FAILURE;
	}

	ObjectTable[index].nameIndex = name_param;*/
}

// Script function #19 (0x13)
void Script::SF_getObjImage(SCRIPTFUNC_PARAMS) {
	error("Not implemented");

/*	int param = getSWord(thread->pop());
	int index = param & 0x1FFF;

	if (index >= ARRAYSIZE(ObjectTable)) {
		thread->retVal = 0;
		return FAILURE;
	}

	thread->retVal = ObjectTable[index].spritelistRn;*/
}

// Script function #20 (0x14)
void Script::SF_getNumber(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_getNumber(), %d args", nArgs);
}

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

	if (_vm->_scene->getFlags() & kSceneFlagISO) {
		//todo: it
	} else {
		_vm->_scene->setDoorState(doorNumber, 0);
	}
}

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

	if (_vm->_scene->getFlags() & kSceneFlagISO) {
		//todo: it
	} 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, ticksToMSec(speed));
	debug(1, "sfSetBgdAnimSpeed(%d, %d)", animId, speed);

}

// Script function #24 (0x18)
void Script::SF_cycleColors(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_cycleColors(), %d args", nArgs);
}

// Script function #25 (0x19)
// Param1: actor id
void Script::sfDoCenterActor(SCRIPTFUNC_PARAMS) {
	int16 actorId;
	actorId = thread->pop();

	_vm->_actor->_centerActor = _vm->_actor->getActor(actorId);
}

// 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->play(animId, ticksToMSec(speed));

	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;
	Location actorLocation;
	ActorData *actor;

	actorId = thread->pop();
	actorLocation.x = thread->pop();
	actorLocation.y = thread->pop();

	actor = _vm->_actor->getActor(actorId);
	actorLocation.z = actor->location.z;

	actor->flags &= ~kFollower;

	_vm->_actor->actorWalkTo(actorId, actorLocation);
}

// Script function #28 (0x1C)
void Script::SF_enableZone(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_enableZone(), %d args", nArgs);
}

// Script function #29 (0x1D)
// Param1: actor id
// Param2: current action
void Script::sfSetActorState(SCRIPTFUNC_PARAMS) {
	int16 actorId;
	int currentAction;
	ActorData *actor;

	actorId = thread->pop();
	currentAction = thread->pop();

	actor = _vm->_actor->getActor(actorId);

	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::scriptMoveTo(SCRIPTFUNC_PARAMS) {
	int16 actorId;
	Location actorLocation;
	ActorData *actor;

	actorId = thread->pop();
	actorLocation.x = thread->pop();
	actorLocation.y = thread->pop();

	actor = _vm->_actor->getActor(actorId);

	actor->location.x = actorLocation.x;
	actor->location.y = actorLocation.y;

}

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

	if (_vm->_scene->getSceneLUT(param) == _vm->_scene->currentSceneNumber())
		thread->_returnValue = 1;
	else 
		thread->_returnValue = 0;
}

// Script function #32 (0x20)
void Script::SF_dropObject(SCRIPTFUNC_PARAMS) {
	error("Not implemented");

/*	ScriptDataWord obj_param = thread->pop();
	ScriptDataWord sprite_param = thread->pop();
	ScriptDataWord x_param = thread->pop();
	ScriptDataWord y_param = thread->pop();

	int index = obj_param & 0x1FFF;

	if (index >= ARRAYSIZE(ObjectTable)) {
		return FAILURE;
	}

	if (ObjectTable[index].sceneIndex == -1) {
		_vm->_interface->removeFromInventory(index);
	}

	ObjectTable[index].sceneIndex = _vm->_scene->currentSceneNumber();
	ObjectTable[index].spritelistRn = 9 + sprite_param;
	ObjectTable[index].x = x_param;
	ObjectTable[index].y = y_param;
*/
}

// 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;
	int16 actorId2;
	ActorData *actor1;
	ActorData *actor2;
	Location location;

	actorId1 = thread->pop();
	actorId2 = thread->pop();

	actor1 = _vm->_actor->getActor(actorId1);
	actor2 = _vm->_actor->getActor(actorId2);
	location = actor1->location;
	actor1->location = actor2->location;
	actor2->location = location;
	

	if (actor1->flags & kProtagonist) {
		actor1->flags &= ~kProtagonist;
		actor2->flags |= kProtagonist;
		_vm->_actor->_protagonist = _vm->_actor->_centerActor = actor2;
	} else {
		if (actor2->flags & kProtagonist) {
		actor2->flags &= ~kProtagonist;
		actor1->flags |= kProtagonist;
		_vm->_actor->_protagonist = _vm->_actor->_centerActor = actor1;
		}
	}


}

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

	stringId = thread->pop();
	actorsCount = thread->pop();

	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();
	
	string = thread->_strings->getString(stringId);

	_vm->_actor->simulSpeech(string, actorsIds, actorsCount, 0);
}

// Script function #36 (0x24) ?
// Param1: actor id
// Param2: actor x
// Param3: actor y
// Param4: actor walk flag
void Script::sfScriptWalk(SCRIPTFUNC_PARAMS) {
	int16 actorId;
	Location actorLocation;
	ActorData *actor;
	uint16 walkFlags;

	actorId = thread->pop();
	actorLocation.x = thread->pop();
	actorLocation.y = thread->pop();
	walkFlags = thread->pop();

	actor = _vm->_actor->getActor(actorId);
	actorLocation.z = actor->location.z;

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

	actor->flags &= ~kFollower;

	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) {
	int16 actorId;
	int flags;
	int cycleFrameSequence;
	int cycleDelay;
	ActorData *actor;

	actorId = thread->pop();
	flags = thread->pop();
	cycleFrameSequence = thread->pop();
	cycleDelay =  thread->pop();

	actor = _vm->_actor->getActor(actorId);

	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) {
		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;
	int frameType;
	int frameOffset;
	ActorData *actor;
	ActorFrameRange *frameRange;

	actorId = thread->pop();
	frameType = thread->pop();
	frameOffset = thread->pop();

	actor = _vm->_actor->getActor(actorId);

	frameRange = _vm->_actor->getActorFrameRange(actorId, frameType);

	if (frameRange->frameCount <= frameOffset) {
	//	frameRange = _vm->_actor->getActorFrameRange(actorId, frameType);

		error("Wrong frameOffset 0x%X", frameOffset);
	}
	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) {
	int16 param = thread->pop();

	_vm->_interface->setRightPortrait(param);
}

// Script function #40 (0x28)
// Sets the left-hand portrait
void Script::sfSetProtagPortrait(SCRIPTFUNC_PARAMS) {
	int16 param = thread->pop();

	_vm->_interface->setLeftPortrait(param);
}

// 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, ticksToMSec(speed));
	}

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

// Script function #42 (0x2A)
void Script::SF_scriptSpecialWalk(SCRIPTFUNC_PARAMS) {
	thread->pop();
	thread->pop();
	thread->pop();
	thread->pop();

	//debug(1, "stub: SF_scriptSpecialWalk(%d, %d, %d, %d)", param1, param2, param3, param4);
}

// 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;
	Location actorLocation;
	int actorDirection;
	int frameType;
	int frameOffset;
	ActorData *actor;
	ActorFrameRange *frameRange;

	actorId = thread->pop();
	actorLocation.x = thread->pop();
	actorLocation.y = thread->pop();
	actorDirection =  thread->pop();
	frameType =  thread->pop();
	frameOffset =  thread->pop();

	debug(1, "sfPlaceActor(%d, %d, %d, %d, %d, %d)", actorId, actorLocation.x, 
		  actorLocation.y, actorDirection, frameType, frameOffset);

	if (_vm->getGameType() == GType_IHNM) {
		warning("Actors aren't implemented for IHNM yet");
		return;
	}

	actor = _vm->_actor->getActor(actorId);
	actor->location.x = actorLocation.x;
	actor->location.y = actorLocation.y;
	actor->facingDirection = actor->actionDirection = actorDirection;

	if (frameType >= 0) {
		frameRange = _vm->_actor->getActorFrameRange(actorId, frameType);
	
		if (frameRange->frameCount <= frameOffset)
			error("Wrong frameOffset 0x%X", frameOffset);

		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::SF_checkUserInterrupt(SCRIPTFUNC_PARAMS) {
	thread->_returnValue = (_skipSpeeches == true);

}

// Script function #45 (0x2D)
void Script::SF_walkRelative(SCRIPTFUNC_PARAMS) {
	thread->pop();
	thread->pop();
	thread->pop();
	thread->pop();
	thread->pop();

	//debug(1, "stub: SF_walkRelative(%d, %d, %d, %d, %d)", param1, param2, param3, param4, param5);
}

// Script function #46 (0x2E)
void Script::SF_moveRelative(SCRIPTFUNC_PARAMS) {
	thread->pop();
	thread->pop();
	thread->pop();
	thread->pop();
	thread->pop();

	//debug(1, "stub: SF_moveRelative(%d, %d, %d, %d, %d)", param1, param2, param3, param4, param5);
}

// Script function #47 (0x2F)
void Script::SF_simulSpeech2(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_simulSpeech2(), %d args", nArgs);
}

static TEXTLIST_ENTRY *placardTextEntry;

// Script function #48 (0x30)
// Param1: string rid
void Script::sfPlacard(SCRIPTFUNC_PARAMS) {
	int stringId;
	SURFACE *back_buf = _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);

	stringId = thread->pop();

	event.type = ONESHOT_EVENT;
	event.code = CURSOR_EVENT;
	event.op = EVENT_HIDE;

	q_event = _vm->_events->queue(&event);

	_vm->_gfx->getCurrentPal(cur_pal);

	event.type = IMMEDIATE_EVENT;
	event.code = PAL_EVENT;
	event.op = EVENT_PALTOBLACK;
	event.time = 0;
	event.duration = kNormalFadeDuration;
	event.data = cur_pal;

	q_event = _vm->_events->chain(q_event, &event);

	event.type = ONESHOT_EVENT;
	event.code = INTERFACE_EVENT;
	event.op = EVENT_CLEAR_STATUS;

	q_event = _vm->_events->chain(q_event, &event);

	event.type = ONESHOT_EVENT;
	event.code = GRAPHICS_EVENT;
	event.op = EVENT_SETFLAG;
	event.param = RF_PLACARD;

	q_event = _vm->_events->chain(q_event, &event);

	event.type = ONESHOT_EVENT;
	event.code = GRAPHICS_EVENT;
	event.op = EVENT_FILL_RECT;
	event.data = back_buf;
	event.param = 138;
	event.param2 = 0;
	event.param3 = _vm->getSceneHeight();
	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.

	TEXTLIST_ENTRY text_entry;
	SCENE_INFO scene_info;

	_vm->_scene->getInfo(&scene_info);

	text_entry.color = _vm->_gfx->getWhite();
	text_entry.effect_color = _vm->_gfx->getBlack();
	text_entry.text_x = _vm->getDisplayWidth() / 2;
	text_entry.text_y = (_vm->getSceneHeight() - _vm->_font->getHeight(MEDIUM_FONT_ID)) / 2;
	text_entry.font_id = MEDIUM_FONT_ID;
	text_entry.flags = FONT_OUTLINE | FONT_CENTERED;
	text_entry.string = thread->_strings->getString(stringId);

	placardTextEntry = _vm->textAddEntry(scene_info.text_list, &text_entry);

	event.type = ONESHOT_EVENT;
	event.code = TEXT_EVENT;
	event.op = EVENT_DISPLAY;
	event.data = placardTextEntry;

	q_event = _vm->_events->chain(q_event, &event);

	_vm->_scene->getBGPal(&pal);

	event.type = IMMEDIATE_EVENT;
	event.code = PAL_EVENT;
	event.op = EVENT_BLACKTOPAL;
	event.time = 0;
	event.duration = kNormalFadeDuration;
	event.data = pal;

	q_event = _vm->_events->chain(q_event, &event);

	event.type = ONESHOT_EVENT;
	event.code = SCRIPT_EVENT;
	event.op = EVENT_THREAD_WAKE;
	event.param = kWaitTypePlacard;

	q_event = _vm->_events->chain(q_event, &event);

}

// Script function #49 (0x31)
void Script::sfPlacardOff(SCRIPTFUNC_PARAMS) {
	static PALENTRY cur_pal[PAL_ENTRIES];
	PALENTRY *pal;
	EVENT event;
	EVENT *q_event;

	thread->wait(kWaitTypePlacard);

	_vm->_interface->restoreMode();

	_vm->_gfx->getCurrentPal(cur_pal);

	event.type = IMMEDIATE_EVENT;
	event.code = PAL_EVENT;
	event.op = EVENT_PALTOBLACK;
	event.time = 0;
	event.duration = kNormalFadeDuration;
	event.data = cur_pal;

	q_event = _vm->_events->queue(&event);

	event.type = ONESHOT_EVENT;
	event.code = GRAPHICS_EVENT;
	event.op = EVENT_CLEARFLAG;
	event.param = RF_PLACARD;

	q_event = _vm->_events->chain(q_event, &event);

	event.type = ONESHOT_EVENT;
	event.code = TEXT_EVENT;
	event.op = EVENT_REMOVE;
	event.data = placardTextEntry;

	q_event = _vm->_events->chain(q_event, &event);

	_vm->_scene->getBGPal(&pal);

	event.type = IMMEDIATE_EVENT;
	event.code = PAL_EVENT;
	event.op = EVENT_BLACKTOPAL;
	event.time = 0;
	event.duration = kNormalFadeDuration;
	event.data = pal;

	q_event = _vm->_events->chain(q_event, &event);

	event.type = ONESHOT_EVENT;
	event.code = CURSOR_EVENT;
	event.op = EVENT_SHOW;

	q_event = _vm->_events->chain(q_event, &event);

	event.type = ONESHOT_EVENT;
	event.code = SCRIPT_EVENT;
	event.op = EVENT_THREAD_WAKE;
	event.param = kWaitTypePlacard;

	q_event = _vm->_events->chain(q_event, &event);

}

// Script function #50 (0x32)
void Script::SF_setProtagState(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_setProtagState(), %d args", nArgs);
}

// 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)
void Script::SF_throwActor(SCRIPTFUNC_PARAMS) {
	thread->pop();
	thread->pop();
	thread->pop();
	thread->pop();
	thread->pop();
	thread->pop();

	//debug(1, "stub: SF_throwActor(%d, %d, %d, %d, %d, %d)", param1, param2, param3, param4, param5, param6);
}

// Script function #53 (0x35)
void Script::SF_waitWalk(SCRIPTFUNC_PARAMS) {
	int16 param = thread->pop();

	debug(1, "stub: SF_waitWalk(%d)", param);
}

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

// Script function #55 (0x37)
void Script::SF_changeActorScene(SCRIPTFUNC_PARAMS) {
	thread->pop();
	thread->pop();

	//debug(1, "stub: SF_changeActorScene(%d, %d)", param1, param2);
}

// Script function #56 (0x38)
void Script::SF_climb(SCRIPTFUNC_PARAMS) {
	thread->pop();
	thread->pop();
	thread->pop();
	thread->pop();

	//debug(1, "stub: SF_climb(%d, %d, %d, %d)", param1, param2, param3, param4);
}

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

	if (_vm->_scene->getFlags() & kSceneFlagISO) {
		//todo: it
	} else {
		_vm->_scene->setDoorState(doorNumber, doorState);
	}
}

// Script function #58 (0x3A)
void Script::SF_setActorZ(SCRIPTFUNC_PARAMS) {
	thread->pop();
	thread->pop();

	//debug(1, "stub: SF_setActorZ(%d, %d)", param1, param2);
}

// Script function #59 (0x3B)
void Script::SF_text(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_text(), %d args", nArgs);
}

// Script function #60 (0x3C)
void Script::SF_getActorX(SCRIPTFUNC_PARAMS) {
	int16 param = thread->pop();

	debug(1, "stub: SF_getActorX(%d)", param);
}

// Script function #61 (0x3D)
void Script::SF_getActorY(SCRIPTFUNC_PARAMS) {
	int16 param = thread->pop();

	debug(1, "stub: SF_getActorY(%d)", param);
}

// Script function #62 (0x3E)
void Script::SF_eraseDelta(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_eraseDelta(), %d args", nArgs);
}

// 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->play(param);
		else
			_vm->_music->stop();
	} else {
		int16 param1 = thread->pop();
		int16 param2 = thread->pop();

		debug(1, "Stub: sfPlayMusic(%d, %d)", param1, param2);
	}

}

// Script function #64 (0x40)
void Script::SF_pickClimbOutPos(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_pickClimbOutPos(), %d args", nArgs);
}

// Script function #65 (0x41)
void Script::SF_tossRif(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_tossRif(), %d args", nArgs);
}

// Script function #66 (0x42)
void Script::SF_showControls(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_showControls(), %d args", nArgs);
}

// Script function #67 (0x43)
void Script::SF_showMap(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_showMap(), %d args", nArgs);
}

// Script function #68 (0x44)
void Script::SF_puzzleWon(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_puzzleWon(), %d args", nArgs);
}

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

static struct {
	int res;
	int vol;
} sfxTable[] = {
	{ FX_DOOR_OPEN,    127 },
	{ FX_DOOR_CLOSE,   127 },
	{ FX_RUSH_WATER,    63 }, // Floppy volume: 127
	{ FX_RUSH_WATER,    26 }, // Floppy volume: 40
	{ FX_CRICKET,       64 },
	{ FX_PORTICULLIS,   84 }, // Floppy volume: 127
	{ FX_CLOCK_1,       64 },
	{ FX_CLOCK_2,       64 },
	{ FX_DAM_MACHINE,   64 },
	{ FX_DAM_MACHINE,   40 },
	{ FX_HUM1,          64 },
	{ FX_HUM2,          64 },
	{ FX_HUM3,          64 },
	{ FX_HUM4,          64 },
	{ FX_WATER_LOOP_S,  32 }, // Floppy volume: 64
	{ FX_SURF,          42 }, // Floppy volume: 127
	{ FX_SURF,          32 }, // Floppy volume: 64
	{ FX_FIRELOOP,      64 }, // Floppy volume: 96
	{ FX_SCRAPING,      84 }, // Floppy volume: 127
	{ FX_BEE_SWARM,     64 }, // Floppy volume: 96
	{ FX_BEE_SWARM,     26 }, // Floppy volume: 40
	{ FX_SQUEAKBOARD,   64 },
	{ FX_KNOCK,        127 },
	{ FX_COINS,         32 }, // Floppy volume: 48
	{ FX_STORM,         84 }, // Floppy volume: 127
	{ FX_DOOR_CLOSE_2,  84 }, // Floppy volume: 127
	{ FX_ARCWELD,       84 }, // Floppy volume: 127
	{ FX_RETRACT_ORB,  127 },
	{ FX_DRAGON,       127 },
	{ FX_SNORES,       127 },
	{ FX_SPLASH,       127 },
	{ FX_LOBBY_DOOR,   127 },
	{ FX_CHIRP_LOOP,    26 }, // Floppy volume: 40
	{ FX_DOOR_CREAK,    96 },
	{ FX_SPOON_DIG,     64 },
	{ FX_CROW,          96 },
	{ FX_COLDWIND,      42 }, // Floppy volume: 64
	{ FX_TOOL_SND_1,    96 },
	{ FX_TOOL_SND_2,   127 },
	{ FX_TOOL_SND_3,    64 },
	{ FX_DOOR_METAL,    96 },
	{ FX_WATER_LOOP_S,  32 },
	{ FX_WATER_LOOP_L,  32 }, // Floppy volume: 64
	{ FX_DOOR_OPEN_2,  127 },
	{ FX_JAIL_DOOR,     64 },
	{ FX_KILN_FIRE,     53 }, // Floppy volume: 80

	// Only in the CD version
	{ FX_CROWD_01,      64 },
	{ FX_CROWD_02,      64 },
	{ FX_CROWD_03,      64 },
	{ FX_CROWD_04,      64 },
	{ FX_CROWD_05,      64 },
	{ FX_CROWD_06,      64 },
	{ FX_CROWD_07,      64 },
	{ FX_CROWD_08,      64 },
	{ FX_CROWD_09,      64 },
	{ FX_CROWD_10,      64 },
	{ FX_CROWD_11,      64 },
	{ FX_CROWD_12,      64 },
	{ FX_CROWD_13,      64 },
	{ FX_CROWD_14,      64 },
	{ FX_CROWD_15,      64 },
	{ FX_CROWD_16,      64 },
	{ FX_CROWD_17,      64 }
};

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

	if (param < ARRAYSIZE(sfxTable)) {
		res = sfxTable[param].res;
		if (_vm->getFeatures() & GF_CD_FX)
			res -= 14;

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

}

// Script function #71 (0x47)
void Script::SF_playLoopedSound(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_playLoopedSound(), %d args", nArgs);
}

// Script function #72 (0x48)
void Script::SF_getDeltaFrame(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_getDeltaFrame(), %d args", nArgs);
}

// Script function #73 (0x49)
void Script::SF_showProtect(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_showProtect(), %d args", nArgs);
}

// Script function #74 (0x4A)
void Script::SF_protectResult(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_protectResult(), %d args", nArgs);
}

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

	thread->_returnValue = (_vm->_rnd.getRandomNumber(param));

}

// Script function #76 (0x4c)
void Script::SF_fadeMusic(SCRIPTFUNC_PARAMS) {
	debug(1, "stub: SF_fadeMusic()");
}

// Script function #77 (0x4d)
void Script::SF_playVoice(SCRIPTFUNC_PARAMS) {
	for (int i = 0; i < nArgs; i++)
		thread->pop();

	debug(1, "stub: SF_playVoice(), %d args", nArgs);
}

void Script::finishDialog(int replyID, int flags, int bitOffset) {
	if (_conversingThread) {
		_vm->_interface->setMode(kPanelNull);

		_conversingThread->_flags &= ~kTFlagWaiting;

		_conversingThread->push(replyID);

		if (flags & kReplyOnce) {
			// TODO:
		}
	}

	_conversingThread = NULL;
	wakeUpThreads(kWaitTypeDialogBegin);
}

} // End of namespace Saga