/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 * This code is based on original Tony Tough source code
 *
 * Copyright (c) 1997-2003 Nayma Software
 */

#include "common/system.h"
#include "common/savefile.h"
#include "tony/mpal/mpal.h"
#include "tony/mpal/memory.h"
#include "tony/custom.h"
#include "tony/font.h"
#include "tony/game.h"
#include "tony/gfxcore.h"
#include "tony/tony.h"
#include "tony/tonychar.h"
#include "tony/utils.h"

namespace Tony {

static const char *const kAmbianceFile[] =  {
	"None",
	"1.ADP",    // Grilli.WAV
	"2.ADP",    // Grilli-Ovattati.WAV
	"3.ADP",    // Grilli-Vento.WAV
	"3.ADP",    // Grilli-Vento1.WAV
	"5.ADP",    // Vento1.WAV
	"4.ADP",    // Mare1.WAV
	"6.ADP"     // Mare1.WAV half volume
};

static const MusicFileEntry kMusicFiles[] =  {
	{"00.ADP", 0},    {"01.ADP", 0},    {"02.ADP", 0},    {"03.ADP", 0},
	{"04.ADP", 0},    {"05.ADP", 0},    {"06.ADP", 0},    {"07.ADP", 0},
	{"08.ADP", 2450}, {"09.ADP", 0},    {"10.ADP", 0},    {"11.ADP", 0},
	{"12.ADP", 0},    {"13.ADP", 0},    {"14.ADP", 0},    {"15.ADP", 0},
	{"16.ADP", 0},    {"17.ADP", 0},    {"18.ADP", 0},    {"19.ADP", 0},
	{"20.ADP", 0},    {"21.ADP", 0},    {"22.ADP", 0},    {"23.ADP", 0},
	{"24.ADP", 0},    {"25.ADP", 0},    {"26.ADP", 0},    {"27.ADP", 0},
	{"28.ADP", 1670}, {"29.ADP", 0},    {"30.ADP", 0},    {"31.ADP", 0},
	{"32.ADP", 2900}, {"33.ADP", 0},    {"34.ADP", 0},    {"35.ADP", 0},
	{"36.ADP", 0},    {"37.ADP", 0},    {"38.ADP", 0},    {"39.ADP", 0},
	{"40.ADP", 0},    {"41.ADP", 1920}, {"42.ADP", 1560}, {"43.ADP", 1920},
	{"44.ADP", 1920}, {"45.ADP", 1920}, {"46.ADP", 1920}, {"47.ADP", 1920},
	{"48.ADP", 1920}, {"49.ADP", 1920}, {"50.ADP", 1920}, {"51.ADP", 1920},
	{"52.ADP", 1920}, {"53.ADP", 0},    {"54.ADP", 0},    {"55.ADP", 0},
	{"56.ADP", 0},    {"57.ADP", 0},    {"58.ADP", 0},    {"59.ADP", 0}
};


static const char *const kJingleFileNames[] = {
	"S00.ADP", "S01.ADP", "S02.ADP", "S03.ADP", "S04.ADP",
	"S05.ADP", "S06.ADP", "S07.ADP", "S08.ADP", "S09.ADP",
	"S10.ADP", "S11.ADP", "S12.ADP", "S13.ADP", "S14.ADP",
	"S15.ADP", "S16.ADP", "S17.ADP", "S18.ADP"
};

void reapplyChangedHotspot() {
	for (int i = 0; i < GLOBALS._curChangedHotspot; i++)
		GLOBALS._loc->getItemFromCode(GLOBALS._changedHotspot[i]._dwCode)->changeHotspot(RMPoint(GLOBALS._changedHotspot[i]._nX, GLOBALS._changedHotspot[i]._nY));
}

void saveChangedHotspot(Common::OutSaveFile *f) {
	f->writeByte(GLOBALS._curChangedHotspot);
	if (GLOBALS._curChangedHotspot > 0) {
		for (int i = 0; i < GLOBALS._curChangedHotspot; ++i)
			GLOBALS._changedHotspot[i].save(f);
	}
}

void loadChangedHotspot(Common::InSaveFile *f) {
	GLOBALS._curChangedHotspot = f->readByte();

	if (GLOBALS._curChangedHotspot > 0) {
		for (int i = 0; i < GLOBALS._curChangedHotspot; ++i)
			GLOBALS._changedHotspot[i].load(f);
	}
}

/**
 * Classes required for custom functions
 *
 * Tony (To Move him)     -> You can do MPAL through the animation? I really think so
 *
 * SendMessage -> I'd say just theEngine.SendMessage()
 * ChangeLocation -> theEngine.ChangeLocation()
 * AddInventory -> theEngine.AddInventory()
*/

void mCharResetCodes() {
	for (int i = 0; i < 10; i++)
		GLOBALS._mCharacter[i]._item = GLOBALS._loc->getItemFromCode(GLOBALS._mCharacter[i]._code);
	for (int i = 0; i < 10; i++)
		GLOBALS._character[i]._item = GLOBALS._loc->getItemFromCode(GLOBALS._character[i]._code);
}

void charsSaveAll(Common::OutSaveFile *f) {
	for (int i = 0; i < 10; i++) {
		f->writeByte(GLOBALS._isMChar[i]);
		if (GLOBALS._isMChar[i]) {
			GLOBALS._mCharacter[i].save(f);
		} else {
			GLOBALS._character[i].save(f);
		}
	}
}

void charsLoadAll(Common::InSaveFile *f) {
	for (int i = 0; i < 10; i++) {
		GLOBALS._isMChar[i] = f->readByte();
		if (GLOBALS._isMChar[i])
			GLOBALS._mCharacter[i].load(f);
		else
			GLOBALS._character[i].load(f);
	}
}

void faceToMe(CORO_PARAM, uint32, uint32, uint32, uint32) {
	GLOBALS._tony->setPattern(GLOBALS._tony->PAT_STANDDOWN);
}

void backToMe(CORO_PARAM, uint32, uint32, uint32, uint32) {
	GLOBALS._tony->setPattern(GLOBALS._tony->PAT_STANDUP);
}

void leftToMe(CORO_PARAM, uint32, uint32, uint32, uint32) {
	GLOBALS._tony->setPattern(GLOBALS._tony->PAT_STANDLEFT);
}

void rightToMe(CORO_PARAM, uint32, uint32, uint32, uint32) {
	GLOBALS._tony->setPattern(GLOBALS._tony->PAT_STANDRIGHT);
}

void tonySetPerorate(CORO_PARAM, uint32 bStatus, uint32, uint32, uint32) {
	g_vm->getEngine()->setPerorate(bStatus);
}

void mySleep(CORO_PARAM, uint32 dwTime, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	int i;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	if (!GLOBALS._bSkipIdle)
		CORO_INVOKE_1(CoroScheduler.sleep, dwTime);

	CORO_END_CODE;
}

void setAlwaysDisplay(CORO_PARAM, uint32 val, uint32, uint32, uint32) {
	GLOBALS._bAlwaysDisplay = (val != 0);
}

void setPointer(CORO_PARAM, uint32 dwPointer, uint32, uint32, uint32) {
	switch (dwPointer) {
	case 1:
		GLOBALS._pointer->setSpecialPointer(GLOBALS._pointer->PTR_ARROWUP);
		break;
	case 2:
		GLOBALS._pointer->setSpecialPointer(GLOBALS._pointer->PTR_ARROWDOWN);
		break;
	case 3:
		GLOBALS._pointer->setSpecialPointer(GLOBALS._pointer->PTR_ARROWLEFT);
		break;
	case 4:
		GLOBALS._pointer->setSpecialPointer(GLOBALS._pointer->PTR_ARROWRIGHT);
		break;
	case 5:
		GLOBALS._pointer->setSpecialPointer(GLOBALS._pointer->PTR_ARROWMAP);
		break;

	default:
		GLOBALS._pointer->setSpecialPointer(GLOBALS._pointer->PTR_NONE);
		break;
	}
}

VoiceHeader *searchVoiceHeader(uint32 codehi, uint32 codelo) {
	int code = (codehi << 16) | codelo;

	if (g_vm->_voices.size() == 0)
		return NULL;

	for (uint i = 0; i < g_vm->_voices.size(); i++) {
		if (g_vm->_voices[i]._code == code)
			return &g_vm->_voices[i];
	}

	return NULL;
}

void sendTonyMessage(CORO_PARAM, uint32 dwMessage, uint32 nX, uint32 nY, uint32) {
	CORO_BEGIN_CONTEXT;
	RMMessage msg;
	int i;
	int curOffset;
	VoiceHeader *curVoc;
	FPSfx *voice;
	RMTextDialog text;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	_ctx->curOffset = 0;

	if (GLOBALS._bSkipIdle)
		return;

	_ctx->msg.load(dwMessage);
	if (!_ctx->msg.isValid())
		return;

	_ctx->curVoc = searchVoiceHeader(0, dwMessage);
	_ctx->voice = NULL;
	if (_ctx->curVoc) {
		// Is positioned within the database of entries beginning at the first
		_ctx->curOffset = _ctx->curVoc->_offset;

		// First time allocation
		g_vm->_vdbFP.seek(_ctx->curOffset);
		g_vm->_theSound.createSfx(&_ctx->voice);

		_ctx->voice->loadVoiceFromVDB(g_vm->_vdbFP);
		_ctx->curOffset = g_vm->_vdbFP.pos();

		_ctx->voice->setLoop(false);
	}

	if (GLOBALS._nTonyNextTalkType != GLOBALS._tony->TALK_NORMAL) {
		CORO_INVOKE_1(GLOBALS._tony->startTalk, GLOBALS._nTonyNextTalkType);

		if (!GLOBALS._bStaticTalk)
			GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL;
	} else {
		if (_ctx->msg.numPeriods() > 1)
			CORO_INVOKE_1(GLOBALS._tony->startTalk, GLOBALS._tony->TALK_HIPS);
		else
			CORO_INVOKE_1(GLOBALS._tony->startTalk, GLOBALS._tony->TALK_NORMAL);
	}

	if (GLOBALS._curBackText)
		CORO_INVOKE_0(GLOBALS._curBackText->hide);

	GLOBALS._bTonyIsSpeaking = true;

	for (_ctx->i = 0; _ctx->i < _ctx->msg.numPeriods() && !GLOBALS._bSkipIdle; _ctx->i++) {
		_ctx->text.setInput(GLOBALS._input);

		// Alignment
		_ctx->text.setAlignType(RMText::HCENTER, RMText::VBOTTOM);

		// Color
		_ctx->text.setColor(0, 255, 0);

		// Writes the text
		_ctx->text.writeText(_ctx->msg[_ctx->i], 0);

		// Set the position
		if (nX == 0 && nY == 0)
			_ctx->text.setPosition(GLOBALS._tony->position() - RMPoint(0, 130) - GLOBALS._loc->scrollPosition());
		else
			_ctx->text.setPosition(RMPoint(nX, nY) - GLOBALS._loc->scrollPosition());

		// Handling for always display
		if (GLOBALS._bAlwaysDisplay) {
			_ctx->text.setAlwaysDisplay();
			_ctx->text.forceTime();
		}

		// Record the text
		g_vm->getEngine()->linkGraphicTask(&_ctx->text);

		if (_ctx->curVoc) {
			if (_ctx->i == 0) {
				_ctx->voice->play();
				_ctx->text.setCustomSkipHandle2(_ctx->voice->_hEndOfBuffer);
			} else {
				g_vm->_vdbFP.seek(_ctx->curOffset);
				g_vm->_theSound.createSfx(&_ctx->voice);
				_ctx->voice->loadVoiceFromVDB(g_vm->_vdbFP);

				_ctx->curOffset = g_vm->_vdbFP.pos();
				_ctx->voice->setLoop(false);
				_ctx->voice->play();
				_ctx->text.setCustomSkipHandle2(_ctx->voice->_hEndOfBuffer);
			}
		}

		// Wait for the end of the display
		_ctx->text.setCustomSkipHandle(GLOBALS._hSkipIdle);
		CORO_INVOKE_0(_ctx->text.waitForEndDisplay);

		if (_ctx->curVoc) {
			_ctx->voice->stop();
			_ctx->voice->release();
			_ctx->voice = NULL;
		}
	}

	GLOBALS._bTonyIsSpeaking = false;
	if (GLOBALS._curBackText)
		GLOBALS._curBackText->show();

	CORO_INVOKE_0(GLOBALS._tony->endTalk);

	CORO_END_CODE;
}

void changeBoxStatus(CORO_PARAM, uint32 nLoc, uint32 nBox, uint32 nStatus, uint32) {
	GLOBALS._boxes->changeBoxStatus(nLoc, nBox, nStatus);
}

void custLoadLocation(CORO_PARAM, uint32 nLoc, uint32 tX, uint32 tY, uint32 bUseStartPos) {
	CORO_BEGIN_CONTEXT;
	uint32 h;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._curChangedHotspot = 0;
	if (bUseStartPos != 0)
		g_vm->getEngine()->loadLocation(nLoc, RMPoint(tX, tY), GLOBALS._startLocPos[nLoc]);
	else
		g_vm->getEngine()->loadLocation(nLoc, RMPoint(tX, tY), RMPoint(-1, -1));

	_ctx->h = mpalQueryDoAction(0, nLoc, 0);

	// On Enter?
	if (_ctx->h != CORO_INVALID_PID_VALUE)
		CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _ctx->h, CORO_INFINITE);

	CORO_END_CODE;
}

void sendFullscreenMsgStart(CORO_PARAM, uint32 nMsg, uint32 nFont, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	RMMessage *msg;
	RMGfxClearTask clear;
	int i;
	RMTextDialog text;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	_ctx->msg = new RMMessage(nMsg);

	GLOBALS._fullScreenMessageLoc = GLOBALS._loc->TEMPGetNumLoc();
	GLOBALS._fullScreenMessagePt = GLOBALS._tony->position();

	if (GLOBALS._bSkipIdle)
		return;

	CORO_INVOKE_2(g_vm->getEngine()->unloadLocation, false, NULL);
	GLOBALS._tony->hide();

	for (_ctx->i = 0; _ctx->i < _ctx->msg->numPeriods() && !GLOBALS._bSkipIdle; _ctx->i++) {
		_ctx->text.setInput(GLOBALS._input);

		// Alignment
		_ctx->text.setAlignType(RMText::HCENTER, RMText::VCENTER);

		// Forces the text to disappear in time
		_ctx->text.forceTime();

		// Color
		_ctx->text.setColor(255, 255, 255);

		// Write the text
		if (nFont == 0)
			_ctx->text.writeText((*_ctx->msg)[_ctx->i], 1);
		else if (nFont == 1)
			_ctx->text.writeText((*_ctx->msg)[_ctx->i], 0);

		// Set the position
		_ctx->text.setPosition(RMPoint(320, 240));

		_ctx->text.setAlwaysDisplay();
		_ctx->text.forceTime();

		// Record the text
		g_vm->getEngine()->linkGraphicTask(&_ctx->clear);
		g_vm->getEngine()->linkGraphicTask(&_ctx->text);

		// Wait for the end of display
		_ctx->text.setCustomSkipHandle(GLOBALS._hSkipIdle);
		CORO_INVOKE_0(_ctx->text.waitForEndDisplay);
	}

	delete _ctx->msg;

	CORO_END_CODE;
}

void clearScreen(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	char buf[256];
	RMGfxClearTask clear;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	g_vm->getEngine()->linkGraphicTask(&_ctx->clear);

	CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE);

	// WORKAROUND: This fixes a bug in the original source where the linked clear task
	// didn't have time to be drawn and removed from the draw list before the method
	// ended, thus remaining in the draw list and causing a later crash
	CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE);

	CORO_END_CODE;
}

void sendFullscreenMsgEnd(CORO_PARAM, uint32 bNotEnableTony, uint32, uint32, uint32) {
	g_vm->getEngine()->loadLocation(GLOBALS._fullScreenMessageLoc, RMPoint(GLOBALS._fullScreenMessagePt._x, GLOBALS._fullScreenMessagePt._y), RMPoint(-1, -1));
	if (!bNotEnableTony)
		GLOBALS._tony->show();

	mCharResetCodes();
	reapplyChangedHotspot();
}


void sendFullscreenMessage(CORO_PARAM, uint32 nMsg, uint32 nFont, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_INVOKE_4(sendFullscreenMsgStart, nMsg, nFont, 0, 0);
	CORO_INVOKE_4(sendFullscreenMsgEnd, 0, 0, 0, 0);

	CORO_END_CODE;
}

void noBullsEye(CORO_PARAM, uint32, uint32, uint32, uint32) {
	GLOBALS._bNoBullsEye = true;
}

void closeLocation(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	if (!GLOBALS._bNoBullsEye) {
		g_vm->getEngine()->initWipe(1);
		CORO_INVOKE_0(g_vm->getEngine()->waitWipeEnd);
	}

	g_vm->stopMusic(4);

	// On exit, unload
	CORO_INVOKE_2(g_vm->getEngine()->unloadLocation, true, NULL);

	CORO_END_CODE;
}

void changeLocation(CORO_PARAM, uint32 nLoc, uint32 tX, uint32 tY, uint32 bUseStartPos) {
	CORO_BEGIN_CONTEXT;
	uint32 h;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	if (!GLOBALS._bNoBullsEye) {
		g_vm->getEngine()->initWipe(1);
		CORO_INVOKE_0(g_vm->getEngine()->waitWipeEnd);
	}

	if (GLOBALS._lastTappeto != GLOBALS._ambiance[nLoc]) {
		g_vm->stopMusic(4);
	}

	// On exit, unfreeze
	CORO_INVOKE_2(g_vm->getEngine()->unloadLocation, true, NULL);

	GLOBALS._curChangedHotspot = 0;
	if (bUseStartPos != 0)
		g_vm->getEngine()->loadLocation(nLoc, RMPoint(tX, tY), GLOBALS._startLocPos[nLoc]);
	else
		g_vm->getEngine()->loadLocation(nLoc, RMPoint(tX, tY), RMPoint(-1, -1));

	if (GLOBALS._lastTappeto != GLOBALS._ambiance[nLoc]) {
		GLOBALS._lastTappeto = GLOBALS._ambiance[nLoc];
		if (GLOBALS._lastTappeto != 0)
			g_vm->playMusic(4, kAmbianceFile[GLOBALS._lastTappeto], 0, true, 2000);
	}

	if (!GLOBALS._bNoBullsEye) {
		g_vm->getEngine()->initWipe(2);
	}

	_ctx->h = mpalQueryDoAction(0, nLoc, 0);

	if (!GLOBALS._bNoBullsEye) {
		CORO_INVOKE_0(g_vm->getEngine()->waitWipeEnd);
		g_vm->getEngine()->closeWipe();
	}

	GLOBALS._bNoBullsEye = false;

	// On Enter?
	if (_ctx->h != CORO_INVALID_PID_VALUE)
		CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _ctx->h, CORO_INFINITE);

	CORO_END_CODE;
}

void setLocStartPosition(CORO_PARAM, uint32 nLoc, uint32 lX, uint32 lY, uint32) {
	GLOBALS._startLocPos[nLoc].set(lX, lY);
}

void saveTonyPosition(CORO_PARAM, uint32, uint32, uint32, uint32) {
	GLOBALS._saveTonyPos = GLOBALS._tony->position();
	GLOBALS._saveTonyLoc = GLOBALS._loc->TEMPGetNumLoc();
}

void restoreTonyPosition(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_INVOKE_4(changeLocation, GLOBALS._saveTonyLoc, GLOBALS._saveTonyPos._x, GLOBALS._saveTonyPos._y, 0);

	mCharResetCodes();

	CORO_END_CODE;
}

void disableInput(CORO_PARAM, uint32, uint32, uint32, uint32) {
	g_vm->getEngine()->disableInput();
}

void enableInput(CORO_PARAM, uint32, uint32, uint32, uint32) {
	g_vm->getEngine()->enableInput();
}

void stopTony(CORO_PARAM, uint32, uint32, uint32, uint32) {
	GLOBALS._tony->stopNoAction(coroParam);
}

void custEnableGUI(CORO_PARAM, uint32, uint32, uint32, uint32) {
	GLOBALS.EnableGUI();
}

void custDisableGUI(CORO_PARAM, uint32, uint32, uint32, uint32) {
	GLOBALS.DisableGUI();
}

void tonyGenericTake1(CORO_PARAM, uint32 nDirection) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._tony->take(nDirection, 0);

	if (!GLOBALS._bSkipIdle)
		CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern);

	CORO_END_CODE;
}

void tonyGenericTake2(CORO_PARAM, uint32 nDirection) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._tony->take(nDirection, 1);

	if (!GLOBALS._bSkipIdle)
		CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern);

	GLOBALS._tony->take(nDirection, 2);

	CORO_END_CODE;
}

void tonyGenericPut1(CORO_PARAM, uint32 nDirection) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._tony->put(nDirection, 0);

	if (!GLOBALS._bSkipIdle)
		CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern);

	CORO_END_CODE;
}

void tonyGenericPut2(CORO_PARAM, uint32 nDirection) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._tony->put(nDirection, 1);

	if (!GLOBALS._bSkipIdle)
		CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern);

	GLOBALS._tony->put(nDirection, 2);

	CORO_END_CODE;
}

void tonyTakeUp1(CORO_PARAM, uint32, uint32, uint32, uint32) {
	tonyGenericTake1(coroParam, 0);
}

void tonyTakeMid1(CORO_PARAM, uint32, uint32, uint32, uint32) {
	tonyGenericTake1(coroParam, 1);
}

void tonyTakeDown1(CORO_PARAM, uint32, uint32, uint32, uint32) {
	tonyGenericTake1(coroParam, 2);
}

void tonyTakeUp2(CORO_PARAM, uint32, uint32, uint32, uint32) {
	tonyGenericTake2(coroParam, 0);
}

void tonyTakeMid2(CORO_PARAM, uint32, uint32, uint32, uint32) {
	tonyGenericTake2(coroParam, 1);
}

void tonyTakeDown2(CORO_PARAM, uint32, uint32, uint32, uint32) {
	tonyGenericTake2(coroParam, 2);
}

void tonyPutUp1(CORO_PARAM, uint32, uint32, uint32, uint32) {
	tonyGenericPut1(coroParam, 0);
}

void tonyPutMid1(CORO_PARAM, uint32, uint32, uint32, uint32) {
	tonyGenericPut1(coroParam, 1);
}

void tonyPutDown1(CORO_PARAM, uint32, uint32, uint32, uint32) {
	tonyGenericPut1(coroParam, 2);
}

void tonyPutUp2(CORO_PARAM, uint32, uint32, uint32, uint32) {
	tonyGenericPut2(coroParam, 0);
}

void tonyPutMid2(CORO_PARAM, uint32, uint32, uint32, uint32) {
	tonyGenericPut2(coroParam, 1);
}

void tonyPutDown2(CORO_PARAM, uint32, uint32, uint32, uint32) {
	tonyGenericPut2(coroParam, 2);
}

void tonyOnTheFloor(CORO_PARAM, uint32 dwParte, uint32, uint32, uint32) {
	if (dwParte == 0)
		GLOBALS._tony->setPattern(GLOBALS._tony->PAT_ONTHEFLOORLEFT);
	else
		GLOBALS._tony->setPattern(GLOBALS._tony->PAT_ONTHEFLOORRIGHT);
}

void tonyGetUp(CORO_PARAM, uint32 dwParte, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	if (dwParte == 0)
		GLOBALS._tony->setPattern(GLOBALS._tony->PAT_GETUPLEFT);
	else
		GLOBALS._tony->setPattern(GLOBALS._tony->PAT_GETUPRIGHT);

	if (!GLOBALS._bSkipIdle)
		CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern);

	CORO_END_CODE;
}

void tonyShepherdess(CORO_PARAM, uint32 bIsPast, uint32, uint32, uint32) {
	GLOBALS._tony->setShepherdess(bIsPast);
}

void tonyWhistle(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._tony->setPattern(GLOBALS._tony->PAT_WHISTLERIGHT);
	if (!GLOBALS._bSkipIdle)
		CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern);

	GLOBALS._tony->setPattern(GLOBALS._tony->PAT_STANDRIGHT);

	CORO_END_CODE;
}

void tonySetNumTexts(uint32 dwText) {
	GLOBALS._dwTonyNumTexts = dwText;
	GLOBALS._bTonyInTexts = false;
}

void tonyLaugh(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_LAUGH;
}

void tonyGiggle(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_LAUGH2;
}

void tonyHips(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_HIPS;
}

void tonySing(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_SING;
}

void tonyIndicate(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_INDICATE;
}

void tonyScaredWithHands(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_SCARED;
}

void tonyScaredWithoutHands(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_SCARED2;
}

void tonyWithHammer(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHHAMMER;
	GLOBALS._tony->setPattern(GLOBALS._tony->PAT_WITHHAMMER);
}

void tonyWithGlasses(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHGLASSES;
	GLOBALS._tony->setPattern(GLOBALS._tony->PAT_WITHGLASSES);
}

void tonyWithWorm(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHWORM;
	GLOBALS._tony->setPattern(GLOBALS._tony->PAT_WITHWORM);
}

void tonyWithRope(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHROPE;
	GLOBALS._tony->setPattern(GLOBALS._tony->PAT_WITHROPE);
}

void tonyWithSecretary(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHSECRETARY;
	GLOBALS._tony->setPattern(GLOBALS._tony->PAT_WITHSECRETARY);
}

void tonyWithRabbitANIM(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHRABBIT;
}

void tonyWithRecipeANIM(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHRECIPE;
}

void tonyWithCardsANIM(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHCARDS;
}

void tonyWithSnowmanANIM(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHSNOWMAN;
}

void tonyWithSnowmanStart(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHSNOWMANSTATIC;
	GLOBALS._bStaticTalk = true;
	CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_WITHSNOWMANSTATIC);

	CORO_END_CODE;
}

void tonyWithSnowmanEnd(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_WITHSNOWMANSTATIC);
	GLOBALS._bStaticTalk = false;
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL;

	CORO_END_CODE;
}

void tonyWithRabbitStart(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHRABBITSTATIC;
	GLOBALS._bStaticTalk = true;
	CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_WITHRABBITSTATIC);

	CORO_END_CODE;
}

void tonyWithRabbitEnd(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_WITHRABBITSTATIC);
	GLOBALS._bStaticTalk = false;
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL;

	CORO_END_CODE;
}

void tonyWithRecipeStart(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHRECIPESTATIC;
	GLOBALS._bStaticTalk = true;
	CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_WITHRECIPESTATIC);

	CORO_END_CODE;
}

void tonyWithRecipeEnd(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_WITHRECIPESTATIC);
	GLOBALS._bStaticTalk = false;
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL;

	CORO_END_CODE;
}

void tonyWithCardsStart(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHCARDSSTATIC;
	GLOBALS._bStaticTalk = true;
	CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_WITHCARDSSTATIC);

	CORO_END_CODE;
}

void tonyWithCardsEnd(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_WITHCARDSSTATIC);
	GLOBALS._bStaticTalk = false;
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL;

	CORO_END_CODE;
}

void tonyWithNotebookStart(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITH_NOTEBOOK;
	GLOBALS._bStaticTalk = true;
	CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_WITH_NOTEBOOK);

	CORO_END_CODE;
}

void tonyWithNotebookEnd(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_WITH_NOTEBOOK);
	GLOBALS._bStaticTalk = false;
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL;

	CORO_END_CODE;
}

void tonyWithMegaphoneStart(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHMEGAPHONESTATIC;
	GLOBALS._bStaticTalk = true;
	CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_WITHMEGAPHONESTATIC);

	CORO_END_CODE;
}

void tonyWithMegaphoneEnd(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_WITHMEGAPHONESTATIC);
	GLOBALS._bStaticTalk = false;
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL;

	CORO_END_CODE;
}

void tonyWithBeardStart(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_WITHBEARDSTATIC;
	GLOBALS._bStaticTalk = true;
	CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_WITHBEARDSTATIC);

	CORO_END_CODE;
}

void tonyWithBeardEnd(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_WITHBEARDSTATIC);
	GLOBALS._bStaticTalk = false;
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL;

	CORO_END_CODE;
}

void tonyScaredStart(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_SCAREDSTATIC;
	GLOBALS._bStaticTalk = true;
	CORO_INVOKE_1(GLOBALS._tony->startStatic, GLOBALS._tony->TALK_SCAREDSTATIC);

	CORO_END_CODE;
}

void tonyScaredEnd(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_INVOKE_1(GLOBALS._tony->endStatic, GLOBALS._tony->TALK_SCAREDSTATIC);
	GLOBALS._bStaticTalk = false;
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL;

	CORO_END_CODE;
}

void tonyDisgusted(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_DISGUSTED;
}

void tonySniffLeft(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._tony->setPattern(GLOBALS._tony->PAT_SNIFF_LEFT);
	CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern);
	CORO_INVOKE_4(leftToMe, 0, 0, 0, 0);

	CORO_END_CODE;
}

void tonySniffRight(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._tony->setPattern(GLOBALS._tony->PAT_SNIFF_RIGHT);
	CORO_INVOKE_0(GLOBALS._tony->waitForEndPattern);
	CORO_INVOKE_4(rightToMe, 0, 0, 0, 0);

	CORO_END_CODE;
}

void tonySarcastic(CORO_PARAM, uint32 dwText, uint32, uint32, uint32) {
	tonySetNumTexts(dwText);
	GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_SARCASTIC;
}

void tonyMacbeth(CORO_PARAM, uint32 nPos, uint32, uint32, uint32) {
	switch (nPos) {
	case 1:
		GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH1;
		break;
	case 2:
		GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH2;
		break;
	case 3:
		GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH3;
		break;
	case 4:
		GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH4;
		break;
	case 5:
		GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH5;
		break;
	case 6:
		GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH6;
		break;
	case 7:
		GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH7;
		break;
	case 8:
		GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH8;
		break;
	case 9:
		GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_MACBETH9;
		break;
	default:
		break;
	}
}

void enableTony(CORO_PARAM, uint32, uint32, uint32, uint32) {
	GLOBALS._tony->show();
}

void disableTony(CORO_PARAM, uint32 bShowShadow, uint32, uint32, uint32) {
	GLOBALS._tony->hide(bShowShadow);
}

void waitForPatternEnd(CORO_PARAM, uint32 nItem, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	RMItem *item;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	_ctx->item = GLOBALS._loc->getItemFromCode(nItem);

	if (!GLOBALS._bSkipIdle && _ctx->item != NULL)
		CORO_INVOKE_1(_ctx->item->waitForEndPattern, GLOBALS._hSkipIdle);

	CORO_END_CODE;
}

void setTonyPosition(CORO_PARAM, uint32 nX, uint32 nY, uint32 nLoc, uint32) {
	GLOBALS._tony->setPosition(RMPoint(nX, nY), nLoc);
}

void moveTonyAndWait(CORO_PARAM, uint32 nX, uint32 nY, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	// WORKAROUND: Delay for a frame before starting the move to give any previous move time to finish.
	// This fixes a bug in the first scene where if you immediately 'Use Door', Tony moves to the door,
	// and then floats to the right rather than properly walking.
	CORO_SLEEP(1);

	CORO_INVOKE_1(GLOBALS._tony->move, RMPoint(nX, nY));

	if (!GLOBALS._bSkipIdle)
		CORO_INVOKE_0(GLOBALS._tony->waitForEndMovement);

	CORO_END_CODE;
}

void moveTony(CORO_PARAM, uint32 nX, uint32 nY, uint32, uint32) {
	GLOBALS._tony->move(coroParam, RMPoint(nX, nY));
}

void scrollLocation(CORO_PARAM, uint32 nX, uint32 nY, uint32 sX, uint32 sY) {
	CORO_BEGIN_CONTEXT;
	int lx, ly;
	RMPoint pt;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	// Take the scroll coordinates
	_ctx->lx = (int32)nX;
	_ctx->ly = (int32)nY;

	_ctx->pt = GLOBALS._loc->scrollPosition();

	while ((_ctx->lx != 0 || _ctx->ly != 0) && !GLOBALS._bSkipIdle) {
		if (_ctx->lx > 0) {
			_ctx->lx -= (int32)sX;
			if (_ctx->lx < 0)
				_ctx->lx = 0;
			_ctx->pt.offset((int32)sX, 0);
		} else if (_ctx->lx < 0) {
			_ctx->lx += (int32)sX;
			if (_ctx->lx > 0)
				_ctx->lx = 0;
			_ctx->pt.offset(-(int32)sX, 0);
		}

		if (_ctx->ly > 0) {
			_ctx->ly -= sY;
			if (_ctx->ly < 0)
				_ctx->ly = 0;
			_ctx->pt.offset(0, sY);
		} else if (_ctx->ly < 0) {
			_ctx->ly += sY;
			if (_ctx->ly > 0)
				_ctx->ly = 0;
			_ctx->pt.offset(0, -(int32)sY);
		}

		CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE);

		GLOBALS._loc->setScrollPosition(_ctx->pt);
		GLOBALS._tony->setScrollPosition(_ctx->pt);
	}

	CORO_END_CODE;
}

void syncScrollLocation(CORO_PARAM, uint32 nX, uint32 nY, uint32 sX, uint32 sY) {
	CORO_BEGIN_CONTEXT;
	int lx, ly;
	RMPoint pt, startpt;
	uint32 dwStartTime, dwCurTime, dwTotalTime;
	uint32 stepX, stepY;
	int dimx, dimy;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	// Take the scroll coordinates
	_ctx->lx = (int32)nX;
	_ctx->ly = (int32)nY;
	_ctx->dimx = _ctx->lx;
	_ctx->dimy = _ctx->ly;
	if (_ctx->lx < 0)
		_ctx->dimx = -_ctx->lx;

	if (_ctx->ly < 0)
		_ctx->dimy = -_ctx->ly;

	_ctx->stepX = sX;
	_ctx->stepY = sY;

	_ctx->startpt = GLOBALS._loc->scrollPosition();

	_ctx->dwStartTime = g_vm->getTime();

	if (sX)
		_ctx->dwTotalTime = _ctx->dimx * (1000 / 35) / sX;
	else
		_ctx->dwTotalTime = _ctx->dimy * (1000 / 35) / sY;

	while ((_ctx->lx != 0 || _ctx->ly != 0) && !GLOBALS._bSkipIdle) {
		_ctx->dwCurTime = g_vm->getTime() - _ctx->dwStartTime;
		if (_ctx->dwCurTime > _ctx->dwTotalTime)
			break;

		_ctx->pt = _ctx->startpt;

		if (sX) {
			if (_ctx->lx > 0)
				_ctx->pt._x += (_ctx->dimx * _ctx->dwCurTime) / _ctx->dwTotalTime;
			else
				_ctx->pt._x -= (_ctx->dimx * _ctx->dwCurTime) / _ctx->dwTotalTime;
		} else {
			if (_ctx->ly > 0)
				_ctx->pt._y += (_ctx->dimy * _ctx->dwCurTime) / _ctx->dwTotalTime;
			else
				_ctx->pt._y -= (_ctx->dimy * _ctx->dwCurTime) / _ctx->dwTotalTime;
		}

		CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE);

		GLOBALS._loc->setScrollPosition(_ctx->pt);
		GLOBALS._tony->setScrollPosition(_ctx->pt);
	}

	// Set the position finale
	if (sX) {
		if (_ctx->lx > 0)
			_ctx->pt._x = _ctx->startpt._x + _ctx->dimx;
		else
			_ctx->pt._x = _ctx->startpt._x - _ctx->dimx;
	} else {
		if (_ctx->ly > 0)
			_ctx->pt._y = _ctx->startpt._y + _ctx->dimy;
		else
			_ctx->pt._y = _ctx->startpt._y - _ctx->dimy;
	}

	GLOBALS._loc->setScrollPosition(_ctx->pt);
	GLOBALS._tony->setScrollPosition(_ctx->pt);

	CORO_END_CODE;
}

void changeHotspot(CORO_PARAM, uint32 dwCode, uint32 nX, uint32 nY, uint32) {
	int i;

	for (i = 0; i < GLOBALS._curChangedHotspot; i++) {
		if (GLOBALS._changedHotspot[i]._dwCode == dwCode) {
			GLOBALS._changedHotspot[i]._nX = nX;
			GLOBALS._changedHotspot[i]._nY = nY;
			break;
		}
	}

	if (i == GLOBALS._curChangedHotspot) {
		GLOBALS._changedHotspot[i]._dwCode = dwCode;
		GLOBALS._changedHotspot[i]._nX = nX;
		GLOBALS._changedHotspot[i]._nY = nY;
		GLOBALS._curChangedHotspot++;
	}

	GLOBALS._loc->getItemFromCode(dwCode)->changeHotspot(RMPoint(nX, nY));
}

void autoSave(CORO_PARAM, uint32, uint32, uint32, uint32) {
	g_vm->autoSave(coroParam);
}

void abortGame(CORO_PARAM, uint32, uint32, uint32, uint32) {
	debug(1, "script called abortGame");
	g_vm->quitGame();
}

void shakeScreen(CORO_PARAM, uint32 nScosse, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	uint32 i;
	uint32 curTime;
	int dirx, diry;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	_ctx->curTime = g_vm->getTime();

	_ctx->dirx = 1;
	_ctx->diry = 1;

	while (g_vm->getTime() < _ctx->curTime + nScosse) {
		CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE);

		GLOBALS._loc->setFixedScroll(RMPoint(1 * _ctx->dirx, 1 * _ctx->diry));
		GLOBALS._tony->setFixedScroll(RMPoint(1 * _ctx->dirx, 1 * _ctx->diry));

		_ctx->i = g_vm->_randomSource.getRandomNumber(2);

		if (_ctx->i == 0 || _ctx->i == 2)
			_ctx->dirx = -_ctx->dirx;
		else if (_ctx->i == 1 || _ctx->i == 2)
			_ctx->diry = -_ctx->diry;
	}

	GLOBALS._loc->setFixedScroll(RMPoint(0, 0));
	GLOBALS._tony->setFixedScroll(RMPoint(0, 0));

	CORO_END_CODE;
}

/*
 *  Characters
 */

void charSetCode(CORO_PARAM, uint32 nChar, uint32 nCode, uint32, uint32) {
	assert(nChar < 16);
	GLOBALS._character[nChar]._code = nCode;
	GLOBALS._character[nChar]._item = GLOBALS._loc->getItemFromCode(nCode);
	GLOBALS._character[nChar]._r = 255;
	GLOBALS._character[nChar]._g = 255;
	GLOBALS._character[nChar]._b = 255;
	GLOBALS._character[nChar]._talkPattern = 0;
	GLOBALS._character[nChar]._startTalkPattern = 0;
	GLOBALS._character[nChar]._endTalkPattern = 0;
	GLOBALS._character[nChar]._standPattern = 0;

	GLOBALS._isMChar[nChar] = false;
}

void charSetColor(CORO_PARAM, uint32 nChar, uint32 r, uint32 g, uint32 b) {
	assert(nChar < 16);
	GLOBALS._character[nChar]._r = r;
	GLOBALS._character[nChar]._g = g;
	GLOBALS._character[nChar]._b = b;
}

void charSetTalkPattern(CORO_PARAM, uint32 nChar, uint32 tp, uint32 sp, uint32) {
	assert(nChar < 16);
	GLOBALS._character[nChar]._talkPattern = tp;
	GLOBALS._character[nChar]._standPattern = sp;
}

void charSetStartEndTalkPattern(CORO_PARAM, uint32 nChar, uint32 sp, uint32 ep, uint32) {
	assert(nChar < 16);
	GLOBALS._character[nChar]._startTalkPattern = sp;
	GLOBALS._character[nChar]._endTalkPattern = ep;
}

void charSendMessage(CORO_PARAM, uint32 nChar, uint32 dwMessage, uint32 bIsBack, uint32) {
	CORO_BEGIN_CONTEXT;
	RMMessage *msg;
	int i;
	RMPoint pt;
	RMTextDialog *text;
	int curOffset;
	VoiceHeader *curVoc;
	FPSfx *voice;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	_ctx->msg = new RMMessage(dwMessage);
	_ctx->curOffset = 0;

	assert(nChar < 16);
	_ctx->pt = GLOBALS._character[nChar]._item->calculatePos() - RMPoint(-60, 20) - GLOBALS._loc->scrollPosition();

	if (GLOBALS._character[nChar]._startTalkPattern != 0) {
		GLOBALS._character[nChar]._item->setPattern(GLOBALS._character[nChar]._startTalkPattern);

		CORO_INVOKE_0(GLOBALS._character[nChar]._item->waitForEndPattern);
	}

	GLOBALS._character[nChar]._item->setPattern(GLOBALS._character[nChar]._talkPattern);

	_ctx->curVoc = searchVoiceHeader(0, dwMessage);
	_ctx->voice = NULL;
	if (_ctx->curVoc) {
		// Position within the database of entries, beginning at the first
		g_vm->_vdbFP.seek(_ctx->curVoc->_offset);
		_ctx->curOffset = _ctx->curVoc->_offset;
	}

	for (_ctx->i = 0; _ctx->i < _ctx->msg->numPeriods() && !GLOBALS._bSkipIdle; _ctx->i++) {
		if (bIsBack) {
			GLOBALS._curBackText = _ctx->text = new RMTextDialogScrolling(GLOBALS._loc);
			if (GLOBALS._bTonyIsSpeaking)
				CORO_INVOKE_0(GLOBALS._curBackText->hide);
		} else
			_ctx->text = new RMTextDialog;

		_ctx->text->setInput(GLOBALS._input);

		// Skipping
		_ctx->text->setSkipStatus(!bIsBack);

		// Alignment
		_ctx->text->setAlignType(RMText::HCENTER, RMText::VBOTTOM);

		// Color
		_ctx->text->setColor(GLOBALS._character[nChar]._r, GLOBALS._character[nChar]._g, GLOBALS._character[nChar]._b);

		// Write the text
		_ctx->text->writeText((*_ctx->msg)[_ctx->i], 0);

		// Set the position
		_ctx->text->setPosition(_ctx->pt);

		// Set the always display
		if (GLOBALS._bAlwaysDisplay) {
			_ctx->text->setAlwaysDisplay();
			_ctx->text->forceTime();
		}

		// Record the text
		g_vm->getEngine()->linkGraphicTask(_ctx->text);

		if (_ctx->curVoc) {
			g_vm->_theSound.createSfx(&_ctx->voice);
			g_vm->_vdbFP.seek(_ctx->curOffset);
			_ctx->voice->loadVoiceFromVDB(g_vm->_vdbFP);
			_ctx->voice->setLoop(false);
			if (bIsBack)
				_ctx->voice->setVolume(55);
			_ctx->voice->play();
			_ctx->text->setCustomSkipHandle2(_ctx->voice->_hEndOfBuffer);
			_ctx->curOffset = g_vm->_vdbFP.pos();
		}

		// Wait for the end of display
		_ctx->text->setCustomSkipHandle(GLOBALS._hSkipIdle);
		CORO_INVOKE_0(_ctx->text->waitForEndDisplay);

		if (_ctx->curVoc) {
			_ctx->voice->stop();
			_ctx->voice->release();
			_ctx->voice = NULL;
		}

		GLOBALS._curBackText = NULL;
		delete _ctx->text;
	}

	if (GLOBALS._character[nChar]._endTalkPattern != 0) {
		GLOBALS._character[nChar]._item->setPattern(GLOBALS._character[nChar]._endTalkPattern);
		CORO_INVOKE_0(GLOBALS._character[nChar]._item->waitForEndPattern);
	}

	GLOBALS._character[nChar]._item->setPattern(GLOBALS._character[nChar]._standPattern);
	delete _ctx->msg;

	CORO_END_CODE;
}

void addInventory(CORO_PARAM, uint32 dwCode, uint32, uint32, uint32) {
	GLOBALS._inventory->addItem(dwCode);
}

void removeInventory(CORO_PARAM, uint32 dwCode, uint32, uint32, uint32) {
	GLOBALS._inventory->removeItem(dwCode);
}

void changeInventoryStatus(CORO_PARAM, uint32 dwCode, uint32 dwStatus, uint32, uint32) {
	GLOBALS._inventory->changeItemStatus(dwCode, dwStatus);
}

/*
 * Master Characters
 */

void mCharSetCode(CORO_PARAM, uint32 nChar, uint32 nCode, uint32, uint32) {
	assert(nChar < 10);
	GLOBALS._mCharacter[nChar]._code = nCode;
	if (nCode == 0)
		GLOBALS._mCharacter[nChar]._item = NULL;
	else
		GLOBALS._mCharacter[nChar]._item = GLOBALS._loc->getItemFromCode(nCode);
	GLOBALS._mCharacter[nChar]._r = 255;
	GLOBALS._mCharacter[nChar]._g = 255;
	GLOBALS._mCharacter[nChar]._b = 255;
	GLOBALS._mCharacter[nChar]._x = -1;
	GLOBALS._mCharacter[nChar]._y = -1;
	GLOBALS._mCharacter[nChar]._bAlwaysBack = 0;

	for (int i = 0; i < 10; i++)
		GLOBALS._mCharacter[nChar]._numTalks[i] = 1;

	GLOBALS._mCharacter[nChar]._curGroup = 0;

	GLOBALS._isMChar[nChar] = true;
}

void mCharResetCode(CORO_PARAM, uint32 nChar, uint32, uint32, uint32) {
	GLOBALS._mCharacter[nChar]._item = GLOBALS._loc->getItemFromCode(GLOBALS._mCharacter[nChar]._code);
}

void mCharSetPosition(CORO_PARAM, uint32 nChar, uint32 nX, uint32 nY, uint32) {
	assert(nChar < 10);
	GLOBALS._mCharacter[nChar]._x = nX;
	GLOBALS._mCharacter[nChar]._y = nY;
}

void mCharSetColor(CORO_PARAM, uint32 nChar, uint32 r, uint32 g, uint32 b) {
	assert(nChar < 10);
	GLOBALS._mCharacter[nChar]._r = r;
	GLOBALS._mCharacter[nChar]._g = g;
	GLOBALS._mCharacter[nChar]._b = b;
}

void mCharSetNumTalksInGroup(CORO_PARAM, uint32 nChar, uint32 nGroup, uint32 nTalks, uint32) {
	assert(nChar < 10);
	assert(nGroup < 10);

	GLOBALS._mCharacter[nChar]._numTalks[nGroup] = nTalks;
}

void mCharSetCurrentGroup(CORO_PARAM, uint32 nChar, uint32 nGroup, uint32, uint32) {
	assert(nChar < 10);
	assert(nGroup < 10);

	GLOBALS._mCharacter[nChar]._curGroup = nGroup;
}

void mCharSetNumTexts(CORO_PARAM, uint32 nChar, uint32 nTexts, uint32, uint32) {
	assert(nChar < 10);

	GLOBALS._mCharacter[nChar]._numTexts = nTexts - 1;
	GLOBALS._mCharacter[nChar]._bInTexts = false;
}

void mCharSetAlwaysBack(CORO_PARAM, uint32 nChar, uint32 bAlwaysBack, uint32, uint32) {
	assert(nChar < 10);

	GLOBALS._mCharacter[nChar]._bAlwaysBack = bAlwaysBack;
}

void mCharSendMessage(CORO_PARAM, uint32 nChar, uint32 dwMessage, uint32 bIsBack, uint32 nFont) {
	CORO_BEGIN_CONTEXT;
	RMMessage *msg;
	int i;
	int parm;
	RMPoint pt;
	uint32 h;
	RMTextDialog *text;
	int curOffset;
	VoiceHeader *curVoc;
	FPSfx *voice;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	_ctx->msg = new RMMessage(dwMessage);
	_ctx->curOffset = 0;

	assert(nChar < 10);

	bIsBack |= GLOBALS._mCharacter[nChar]._bAlwaysBack ? 1 : 0;

	// Calculates the position of the text according to the current frame
	if (GLOBALS._mCharacter[nChar]._x == -1)
		_ctx->pt = GLOBALS._mCharacter[nChar]._item->calculatePos() - RMPoint(-60, 20) - GLOBALS._loc->scrollPosition();
	else
		_ctx->pt = RMPoint(GLOBALS._mCharacter[nChar]._x, GLOBALS._mCharacter[nChar]._y);

	// Parameter for special actions: random between the spoken
	_ctx->parm = (GLOBALS._mCharacter[nChar]._curGroup * 10) + g_vm->_randomSource.getRandomNumber(
	                 GLOBALS._mCharacter[nChar]._numTalks[GLOBALS._mCharacter[nChar]._curGroup] - 1) + 1;

	// Try to run the custom function to initialize the speech
	if (GLOBALS._mCharacter[nChar]._item) {
		_ctx->h = mpalQueryDoAction(30, GLOBALS._mCharacter[nChar]._item->mpalCode(), _ctx->parm);
		if (_ctx->h != CORO_INVALID_PID_VALUE) {
			CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _ctx->h, CORO_INFINITE);
		}
	}

	_ctx->curVoc = searchVoiceHeader(0, dwMessage);
	_ctx->voice = NULL;
	if (_ctx->curVoc) {
		// Position within the database of entries, beginning at the first
		g_vm->_vdbFP.seek(_ctx->curVoc->_offset);
		_ctx->curOffset = _ctx->curVoc->_offset;
	}

	for (_ctx->i = 0; _ctx->i < _ctx->msg->numPeriods() && !GLOBALS._bSkipIdle; _ctx->i++) {
		// Create a different object depending on whether it's background or not
		if (bIsBack) {
			GLOBALS._curBackText = _ctx->text = new RMTextDialogScrolling(GLOBALS._loc);
			if (GLOBALS._bTonyIsSpeaking)
				CORO_INVOKE_0(GLOBALS._curBackText->hide);
		} else
			_ctx->text = new RMTextDialog;

		_ctx->text->setInput(GLOBALS._input);

		// Skipping
		_ctx->text->setSkipStatus(!bIsBack);

		// Alignment
		_ctx->text->setAlignType(RMText::HCENTER, RMText::VBOTTOM);

		// Color
		_ctx->text->setColor(GLOBALS._mCharacter[nChar]._r, GLOBALS._mCharacter[nChar]._g, GLOBALS._mCharacter[nChar]._b);

		// Write the text
		_ctx->text->writeText((*_ctx->msg)[_ctx->i], nFont);

		// Set the position
		_ctx->text->setPosition(_ctx->pt);

		// Set the always display
		if (GLOBALS._bAlwaysDisplay) {
			_ctx->text->setAlwaysDisplay();
			_ctx->text->forceTime();
		}

		// Record the text
		g_vm->getEngine()->linkGraphicTask(_ctx->text);

		if (_ctx->curVoc) {
			g_vm->_theSound.createSfx(&_ctx->voice);
			g_vm->_vdbFP.seek(_ctx->curOffset);
			_ctx->voice->loadVoiceFromVDB(g_vm->_vdbFP);
			_ctx->voice->setLoop(false);
			if (bIsBack)
				_ctx->voice->setVolume(55);
			_ctx->voice->play();
			_ctx->text->setCustomSkipHandle2(_ctx->voice->_hEndOfBuffer);
			_ctx->curOffset = g_vm->_vdbFP.pos();
		}

		// Wait for the end of display
		_ctx->text->setCustomSkipHandle(GLOBALS._hSkipIdle);
		CORO_INVOKE_0(_ctx->text->waitForEndDisplay);

		if (_ctx->curVoc) {
			_ctx->voice->stop();
			_ctx->voice->release();
			_ctx->voice = NULL;
		}

		GLOBALS._curBackText = NULL;
		delete _ctx->text;
	}

	delete _ctx->msg;

	// Try to run the custom function to close the speech
	if (GLOBALS._mCharacter[nChar]._item) {
		_ctx->h = mpalQueryDoAction(31, GLOBALS._mCharacter[nChar]._item->mpalCode(), _ctx->parm);
		if (_ctx->h != CORO_INVALID_PID_VALUE)
			CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _ctx->h, CORO_INFINITE);
	}

	CORO_END_CODE;
}

/*
 *  Dialogs
 */

void sendDialogMessage(CORO_PARAM, uint32 nPers, uint32 nMsg, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	char *string;
	RMTextDialog *text;
	int parm;
	uint32 h;
	bool bIsBack;
	VoiceHeader *curVoc;
	FPSfx *voice;
	RMPoint pt;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	_ctx->bIsBack = false;

	// The SendDialogMessage can go in the background if it is a character
	if (nPers != 0 && GLOBALS._isMChar[nPers] && GLOBALS._mCharacter[nPers]._bAlwaysBack)
		_ctx->bIsBack = true;

	_ctx->curVoc = searchVoiceHeader(GLOBALS._curDialog, nMsg);
	_ctx->voice = NULL;

	if (_ctx->curVoc) {
		// Position within the database of entries, beginning at the first
		g_vm->_vdbFP.seek(_ctx->curVoc->_offset);
		g_vm->_theSound.createSfx(&_ctx->voice);
		_ctx->voice->loadVoiceFromVDB(g_vm->_vdbFP);
		_ctx->voice->setLoop(false);
		if (_ctx->bIsBack)
			_ctx->voice->setVolume(55);
	}

	_ctx->string = mpalQueryDialogPeriod(nMsg);

	if (nPers == 0) {
		_ctx->text = new RMTextDialog;
		_ctx->text->setColor(0, 255, 0);
		_ctx->text->setPosition(GLOBALS._tony->position() - RMPoint(0, 130) - GLOBALS._loc->scrollPosition());
		_ctx->text->writeText(_ctx->string, 0);

		if (GLOBALS._dwTonyNumTexts > 0) {
			if (!GLOBALS._bTonyInTexts) {
				if (GLOBALS._nTonyNextTalkType != GLOBALS._tony->TALK_NORMAL) {
					CORO_INVOKE_1(GLOBALS._tony->startTalk, GLOBALS._nTonyNextTalkType);
					if (!GLOBALS._bStaticTalk)
						GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL;
				} else
					CORO_INVOKE_1(GLOBALS._tony->startTalk, GLOBALS._tony->TALK_NORMAL);

				GLOBALS._bTonyInTexts = true;
			}
			GLOBALS._dwTonyNumTexts--;
		} else {
			CORO_INVOKE_1(GLOBALS._tony->startTalk, GLOBALS._nTonyNextTalkType);
			if (!GLOBALS._bStaticTalk)
				GLOBALS._nTonyNextTalkType = GLOBALS._tony->TALK_NORMAL;
		}
	} else if (!GLOBALS._isMChar[nPers]) {
		_ctx->text = new RMTextDialog;

		_ctx->pt = GLOBALS._character[nPers]._item->calculatePos() - RMPoint(-60, 20) - GLOBALS._loc->scrollPosition();

		if (GLOBALS._character[nPers]._startTalkPattern != 0) {
			GLOBALS._character[nPers]._item->setPattern(GLOBALS._character[nPers]._startTalkPattern);
			CORO_INVOKE_0(GLOBALS._character[nPers]._item->waitForEndPattern);
		}

		GLOBALS._character[nPers]._item->setPattern(GLOBALS._character[nPers]._talkPattern);

		_ctx->text->setColor(GLOBALS._character[nPers]._r, GLOBALS._character[nPers]._g, GLOBALS._character[nPers]._b);
		_ctx->text->writeText(_ctx->string, 0);
		_ctx->text->setPosition(_ctx->pt);
	} else {
		if (GLOBALS._mCharacter[nPers]._x == -1)
			_ctx->pt = GLOBALS._mCharacter[nPers]._item->calculatePos() - RMPoint(-60, 20) - GLOBALS._loc->scrollPosition();
		else
			_ctx->pt = RMPoint(GLOBALS._mCharacter[nPers]._x, GLOBALS._mCharacter[nPers]._y);

		// Parameter for special actions. Random between the spoken.
		_ctx->parm = (GLOBALS._mCharacter[nPers]._curGroup * 10) + g_vm->_randomSource.getRandomNumber(
		                 GLOBALS._mCharacter[nPers]._numTalks[GLOBALS._mCharacter[nPers]._curGroup] - 1) + 1;

		if (GLOBALS._mCharacter[nPers]._numTexts != 0 && GLOBALS._mCharacter[nPers]._bInTexts) {
			GLOBALS._mCharacter[nPers]._numTexts--;
		} else {
			// Try to run the custom function to initialize the speech
			_ctx->h = mpalQueryDoAction(30, GLOBALS._mCharacter[nPers]._item->mpalCode(), _ctx->parm);
			if (_ctx->h != CORO_INVALID_PID_VALUE)
				CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _ctx->h, CORO_INFINITE);

			GLOBALS._mCharacter[nPers]._curTalk = _ctx->parm;

			if (GLOBALS._mCharacter[nPers]._numTexts != 0) {
				GLOBALS._mCharacter[nPers]._bInTexts = true;
				GLOBALS._mCharacter[nPers]._numTexts--;
			}
		}

		if (GLOBALS._mCharacter[nPers]._bAlwaysBack) {
			_ctx->text = GLOBALS._curBackText = new RMTextDialogScrolling(GLOBALS._loc);
			if (GLOBALS._bTonyIsSpeaking)
				CORO_INVOKE_0(GLOBALS._curBackText->hide);

			_ctx->bIsBack = true;
		} else
			_ctx->text = new RMTextDialog;

		_ctx->text->setSkipStatus(!GLOBALS._mCharacter[nPers]._bAlwaysBack);
		_ctx->text->setColor(GLOBALS._mCharacter[nPers]._r, GLOBALS._mCharacter[nPers]._g, GLOBALS._mCharacter[nPers]._b);
		_ctx->text->writeText(_ctx->string, 0);
		_ctx->text->setPosition(_ctx->pt);
	}

	if (!GLOBALS._bSkipIdle) {
		_ctx->text->setInput(GLOBALS._input);
		if (GLOBALS._bAlwaysDisplay) {
			_ctx->text->setAlwaysDisplay();
			_ctx->text->forceTime();
		}
		_ctx->text->setAlignType(RMText::HCENTER, RMText::VBOTTOM);
		g_vm->getEngine()->linkGraphicTask(_ctx->text);

		if (_ctx->curVoc) {
			_ctx->voice->play();
			_ctx->text->setCustomSkipHandle2(_ctx->voice->_hEndOfBuffer);
		}

		// Wait for the end of display
		_ctx->text->setCustomSkipHandle(GLOBALS._hSkipIdle);
		CORO_INVOKE_0(_ctx->text->waitForEndDisplay);
	}

	if (_ctx->curVoc) {
		_ctx->voice->stop();
		_ctx->voice->release();
		_ctx->voice = NULL;
	}

	if (nPers != 0) {
		if (!GLOBALS._isMChar[nPers]) {
			if (GLOBALS._character[nPers]._endTalkPattern != 0) {
				GLOBALS._character[nPers]._item->setPattern(GLOBALS._character[nPers]._endTalkPattern);
				CORO_INVOKE_0(GLOBALS._character[nPers]._item->waitForEndPattern);
			}

			GLOBALS._character[nPers]._item->setPattern(GLOBALS._character[nPers]._standPattern);
			delete _ctx->text;
		} else {
			if ((GLOBALS._mCharacter[nPers]._bInTexts && GLOBALS._mCharacter[nPers]._numTexts == 0) || !GLOBALS._mCharacter[nPers]._bInTexts) {
				// Try to run the custom function to close the speech
				GLOBALS._mCharacter[nPers]._curTalk = (GLOBALS._mCharacter[nPers]._curTalk % 10) + GLOBALS._mCharacter[nPers]._curGroup * 10;
				_ctx->h = mpalQueryDoAction(31, GLOBALS._mCharacter[nPers]._item->mpalCode(), GLOBALS._mCharacter[nPers]._curTalk);
				if (_ctx->h != CORO_INVALID_PID_VALUE)
					CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _ctx->h, CORO_INFINITE);

				GLOBALS._mCharacter[nPers]._bInTexts = false;
				GLOBALS._mCharacter[nPers]._numTexts = 0;
			}

			GLOBALS._curBackText = NULL;
			delete _ctx->text;
		}
	} else {
		if ((GLOBALS._dwTonyNumTexts == 0 && GLOBALS._bTonyInTexts) || !GLOBALS._bTonyInTexts) {
			CORO_INVOKE_0(GLOBALS._tony->endTalk);
			GLOBALS._dwTonyNumTexts = 0;
			GLOBALS._bTonyInTexts = false;
		}

		delete _ctx->text;
	}

	globalDestroy(_ctx->string);

	CORO_END_CODE;
}

// @@@@ This cannot be skipped!!!!!!!!!!!!!!!!!!!

void startDialog(CORO_PARAM, uint32 nDialog, uint32 nStartGroup, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	uint32 nChoice;
	uint32 *sl;
	uint32 i, num;
	char *string;
	RMDialogChoice dc;
	int sel;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GLOBALS._curDialog = nDialog;

	// Call MPAL to start the dialog
	mpalQueryDoDialog(nDialog, nStartGroup);

	// Wait until a choice is selected
	mpalQueryDialogWaitForChoice(&_ctx->nChoice);
	while (_ctx->nChoice != (uint32) - 1) {
		// Get the list of options
		_ctx->sl = mpalQueryDialogSelectList(_ctx->nChoice);
		for (_ctx->num = 0; _ctx->sl[_ctx->num] != 0; _ctx->num++)
			;

		// If there is only one option, do it automatically, and wait for the next choice
		if (_ctx->num == 1) {
			mpalQueryDialogSelectionDWORD(_ctx->nChoice, _ctx->sl[0]);
			globalDestroy(_ctx->sl);

			// Wait for the next choice to be made
			mpalQueryDialogWaitForChoice(&_ctx->nChoice);
			continue;
		}

		// Making a choice for dialog
		_ctx->dc.init();
		_ctx->dc.setNumChoices(_ctx->num);

		// Writeall the possible options
		for (_ctx->i = 0; _ctx->i < _ctx->num; _ctx->i++) {
			_ctx->string = mpalQueryDialogPeriod(_ctx->sl[_ctx->i]);
			assert(_ctx->string != NULL);
			_ctx->dc.addChoice(_ctx->string);
			globalDestroy(_ctx->string);
		}

		// Activate the object
		g_vm->getEngine()->linkGraphicTask(&_ctx->dc);
		CORO_INVOKE_0(_ctx->dc.show);

		// Draw the pointer
		GLOBALS._pointer->setSpecialPointer(GLOBALS._pointer->PTR_NONE);
		g_vm->getEngine()->enableMouse();

		while (!(GLOBALS._input->mouseLeftClicked() && ((_ctx->sel = _ctx->dc.getSelection()) != -1))) {
			CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE);
			CORO_INVOKE_1(_ctx->dc.doFrame, GLOBALS._input->mousePos());
		}

		// Hide the pointer
		g_vm->getEngine()->disableMouse();

		CORO_INVOKE_0(_ctx->dc.hide);
		mpalQueryDialogSelectionDWORD(_ctx->nChoice, _ctx->sl[_ctx->sel]);

		// Closes the choice
		_ctx->dc.close();

		globalDestroy(_ctx->sl);

		// Wait for the next choice to be made
		mpalQueryDialogWaitForChoice(&_ctx->nChoice);
	}

	CORO_END_CODE;
}

/*
 *  Sync between idle and mpal
 */

void takeOwnership(CORO_PARAM, uint32 num, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	if (GLOBALS._mut[num]._ownerPid != (uint32)CoroScheduler.getCurrentPID()) {
		// The mutex is currently owned by a different process.
		// Wait for the event to be signalled, which means the mutex is free.
		CORO_INVOKE_2(CoroScheduler.waitForSingleObject, GLOBALS._mut[num]._eventId, CORO_INFINITE);
		GLOBALS._mut[num]._ownerPid = (uint32)CoroScheduler.getCurrentPID();
	}

	GLOBALS._mut[num]._lockCount++;

	CORO_END_CODE;
}

void releaseOwnership(CORO_PARAM, uint32 num, uint32, uint32, uint32) {
	if (!GLOBALS._mut[num]._lockCount) {
		warning("ReleaseOwnership tried to release mutex %d, which isn't held", num);
		return;
	}

	if (GLOBALS._mut[num]._ownerPid != (uint32)CoroScheduler.getCurrentPID()) {
		warning("ReleaseOwnership tried to release mutex %d, which is held by a different process", num);
		return;
	}

	GLOBALS._mut[num]._lockCount--;
	if (!GLOBALS._mut[num]._lockCount) {
		GLOBALS._mut[num]._ownerPid = 0;

		// Signal the event, to wake up processes waiting for the lock.
		CoroScheduler.setEvent(GLOBALS._mut[num]._eventId);
	}
}

/*
 *  Music
 *  -----
 *
 * Fadeout effects supposed:
 *
 *   nFX = 0 - The new music replaces the old one
 *   nFX=1   - The new music interfades with the old one
 *   nFX=2   - The new music takes over in time from the old
 *
 */

void threadFadeInMusic(CORO_PARAM, const void *nMusic) {
	CORO_BEGIN_CONTEXT;
	int i;
	CORO_END_CONTEXT(_ctx);

	int nChannel = *(const int *)nMusic;

	CORO_BEGIN_CODE(_ctx);

	debugC(DEBUG_INTERMEDIATE, kTonyDebugSound, "Start FadeIn Music");

	for (_ctx->i = 0; _ctx->i < 16; _ctx->i++) {
		g_vm->setMusicVolume(nChannel, _ctx->i * 4);

		CORO_INVOKE_1(CoroScheduler.sleep, 100);
	}
	g_vm->setMusicVolume(nChannel, 64);

	debugC(DEBUG_INTERMEDIATE, kTonyDebugSound, "End FadeIn Music");

	CORO_KILL_SELF();

	CORO_END_CODE;
}

void threadFadeOutMusic(CORO_PARAM, const void *nMusic) {
	CORO_BEGIN_CONTEXT;
	int i;
	int startVolume;
	CORO_END_CONTEXT(_ctx);

	int nChannel = *(const int *)nMusic;

	CORO_BEGIN_CODE(_ctx);

	_ctx->startVolume = g_vm->getMusicVolume(nChannel);

	for (_ctx->i = 16; _ctx->i > 0 && !GLOBALS._bFadeOutStop; _ctx->i--) {
		if (_ctx->i * 4 < _ctx->startVolume)
			g_vm->setMusicVolume(nChannel, _ctx->i * 4);

		CORO_INVOKE_1(CoroScheduler.sleep, 100);
	}

	if (!GLOBALS._bFadeOutStop)
		g_vm->setMusicVolume(nChannel, 0);

	// If a jingle is played, stop it
	if (nChannel == 2)
		g_vm->stopMusic(2);

	CORO_KILL_SELF();

	CORO_END_CODE;
}

void fadeInSoundEffect(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CoroScheduler.createProcess(threadFadeInMusic, &GLOBALS._curSoundEffect, sizeof(int));
}

void fadeOutSoundEffect(CORO_PARAM, uint32, uint32, uint32, uint32) {
	GLOBALS._bFadeOutStop = false;
	CoroScheduler.createProcess(threadFadeOutMusic, &GLOBALS._curSoundEffect, sizeof(int));
}

void fadeOutJingle(CORO_PARAM, uint32, uint32, uint32, uint32) {
	GLOBALS._bFadeOutStop = false;
	int channel = 2;
	CoroScheduler.createProcess(threadFadeOutMusic, &channel, sizeof(int));
}

void fadeInJingle(CORO_PARAM, uint32, uint32, uint32, uint32) {
	int channel = 2;
	CoroScheduler.createProcess(threadFadeInMusic, &channel, sizeof(int));
}

void stopSoundEffect(CORO_PARAM, uint32, uint32, uint32, uint32) {
	g_vm->stopMusic(GLOBALS._curSoundEffect);
}

void stopJingle(CORO_PARAM, uint32, uint32, uint32, uint32) {
	g_vm->stopMusic(2);
}

void muteSoundEffect(CORO_PARAM, uint32, uint32, uint32, uint32) {
	g_vm->setMusicVolume(GLOBALS._curSoundEffect, 0);
}

void demuteSoundEffect(CORO_PARAM, uint32, uint32, uint32, uint32) {
	GLOBALS._bFadeOutStop = true;
	g_vm->setMusicVolume(GLOBALS._curSoundEffect, 64);
}

void muteJingle(CORO_PARAM, uint32, uint32, uint32, uint32) {
	g_vm->setMusicVolume(2, 0);
}

void demuteJingle(CORO_PARAM, uint32, uint32, uint32, uint32) {
	g_vm->setMusicVolume(2, 64);
}

void custPlayMusic(uint32 nChannel, const char *mFN, uint32 nFX, bool bLoop, int nSync = 0) {
	if (nSync == 0)
		nSync = 2000;
	debugC(DEBUG_INTERMEDIATE, kTonyDebugMusic, "Start CustPlayMusic");
	g_vm->playMusic(nChannel, mFN, nFX, bLoop, nSync);
	debugC(DEBUG_INTERMEDIATE, kTonyDebugMusic, "End CustPlayMusic");
}

void playSoundEffect(CORO_PARAM, uint32 nMusic, uint32 nFX, uint32 bNoLoop, uint32) {
	if (nFX == 0 || nFX == 1 || nFX == 2) {
		debugC(DEBUG_INTERMEDIATE, kTonyDebugSound, "PlaySoundEffect stop fadeout");
		GLOBALS._bFadeOutStop = true;
	}

	GLOBALS._lastMusic = nMusic;
	custPlayMusic(GLOBALS._curSoundEffect, kMusicFiles[nMusic]._name, nFX, bNoLoop ? false : true, kMusicFiles[nMusic]._sync);
}

void playJingle(CORO_PARAM, uint32 nMusic, uint32 nFX, uint32 bLoop, uint32) {
	custPlayMusic(2, kJingleFileNames[nMusic], nFX, bLoop);
}

void playItemSfx(CORO_PARAM, uint32 nItem, uint32 nSFX, uint32, uint32) {
	if (nItem == 0) {
		GLOBALS._tony->playSfx(nSFX);
	} else {
		RMItem *item = GLOBALS._loc->getItemFromCode(nItem);
		if (item)
			item->playSfx(nSFX);
	}
}

void restoreMusic(CORO_PARAM) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_INVOKE_4(playSoundEffect, GLOBALS._lastMusic, 0, 0, 0);

	if (GLOBALS._lastTappeto != 0)
		custPlayMusic(4, kAmbianceFile[GLOBALS._lastTappeto], 0, true);

	CORO_END_CODE;
}

void saveMusic(Common::OutSaveFile *f) {
	f->writeByte(GLOBALS._lastMusic);
	f->writeByte(GLOBALS._lastTappeto);
}

void loadMusic(Common::InSaveFile *f) {
	GLOBALS._lastMusic = f->readByte();
	GLOBALS._lastTappeto = f->readByte();
}

void jingleFadeStart(CORO_PARAM, uint32 nJingle, uint32 bLoop, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_INVOKE_4(fadeOutSoundEffect, 0, 0, 0, 0);
	CORO_INVOKE_4(muteJingle, 0, 0, 0, 0);
	CORO_INVOKE_4(playJingle, nJingle, 0, bLoop, 0);
	CORO_INVOKE_4(fadeInJingle, 0, 0, 0, 0);

	CORO_END_CODE;
}

void jingleFadeEnd(CORO_PARAM, uint32 nJingle, uint32 bLoop, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_INVOKE_4(fadeOutJingle, 0, 0, 0, 0);
	CORO_INVOKE_4(fadeInSoundEffect, 0, 0, 0, 0);

	CORO_END_CODE;
}

void mustSkipIdleStart(CORO_PARAM, uint32, uint32, uint32, uint32) {
	GLOBALS._bSkipIdle = true;
	CoroScheduler.setEvent(GLOBALS._hSkipIdle);
}

void mustSkipIdleEnd(CORO_PARAM, uint32, uint32, uint32, uint32) {
	GLOBALS._bSkipIdle = false;
	CoroScheduler.resetEvent(GLOBALS._hSkipIdle);
}

void patIrqFreeze(CORO_PARAM, uint32 bStatus, uint32, uint32, uint32) {
	// Unused in ScummVM.
}

void openInitLoadMenu(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_INVOKE_0(g_vm->openInitLoadMenu);

	CORO_END_CODE;
}

void openInitOptions(CORO_PARAM, uint32, uint32, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_INVOKE_0(g_vm->openInitOptions);

	CORO_END_CODE;
}

void doCredits(CORO_PARAM, uint32 nMsg, uint32 dwTime, uint32, uint32) {
	CORO_BEGIN_CONTEXT;
	RMMessage *msg;
	RMTextDialog *text;
	uint32 hDisable;
	int i;
	uint32 startTime;

	~CoroContextTag() override {
		delete msg;
		delete[] text;
	}

	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	_ctx->msg = new RMMessage(nMsg);
	_ctx->hDisable = CoroScheduler.createEvent(true, false);

	_ctx->text = new RMTextDialog[_ctx->msg->numPeriods()];

	for (_ctx->i = 0; _ctx->i < _ctx->msg->numPeriods(); _ctx->i++)     {
		_ctx->text[_ctx->i].setInput(GLOBALS._input);

		// Alignment
		if ((*_ctx->msg)[_ctx->i][0] == '@') {
			_ctx->text[_ctx->i].setAlignType(RMText::HCENTER, RMText::VTOP);
			_ctx->text[_ctx->i].writeText(&(*_ctx->msg)[_ctx->i][1], 3);
			_ctx->text[_ctx->i].setPosition(RMPoint(414, 70 + _ctx->i * 26));  // 70
		} else {
			_ctx->text[_ctx->i].setAlignType(RMText::HLEFT, RMText::VTOP);
			_ctx->text[_ctx->i].writeText((*_ctx->msg)[_ctx->i], 3);
			_ctx->text[_ctx->i].setPosition(RMPoint(260, 70 + _ctx->i * 26));
		}

		// Set the position
		_ctx->text[_ctx->i].setAlwaysDisplay();
		_ctx->text[_ctx->i].setForcedTime(dwTime * 1000);
		_ctx->text[_ctx->i].setNoTab();

		// Wait for the end of display
		_ctx->text[_ctx->i].setCustomSkipHandle(_ctx->hDisable);

		// Record the text
		g_vm->getEngine()->linkGraphicTask(&_ctx->text[_ctx->i]);
	}

	_ctx->startTime = g_vm->getTime();

	while (_ctx->startTime + dwTime * 1000 > g_vm->getTime()) {
		CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE);
		if (GLOBALS._input->mouseLeftClicked() || GLOBALS._input->mouseRightClicked())
			break;
		if (g_vm->getEngine()->getInput().getAsyncKeyState(Common::KEYCODE_TAB))
			break;
	}

	CoroScheduler.setEvent(_ctx->hDisable);

	CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE);
	CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE);

	delete[] _ctx->text;
	delete _ctx->msg;
	_ctx->text = NULL;
	_ctx->msg = NULL;

	CORO_END_CODE;
}

BEGIN_CUSTOM_FUNCTION_MAP()

ASSIGN(1,   custLoadLocation)
ASSIGN(2,   mySleep)
ASSIGN(3,   setPointer)
ASSIGN(5,   moveTony)
ASSIGN(6,   faceToMe)
ASSIGN(7,   backToMe)
ASSIGN(8,   leftToMe)
ASSIGN(9,   rightToMe)
ASSIGN(10,  sendTonyMessage)
ASSIGN(11,  changeBoxStatus)
ASSIGN(12,  changeLocation)
ASSIGN(13,  disableTony)
ASSIGN(14,  enableTony)
ASSIGN(15,  waitForPatternEnd)
ASSIGN(16,  setLocStartPosition)
ASSIGN(17,  scrollLocation)
ASSIGN(18,  moveTonyAndWait)
ASSIGN(19,  changeHotspot)
ASSIGN(20,  addInventory)
ASSIGN(21,  removeInventory)
ASSIGN(22,  changeInventoryStatus)
ASSIGN(23,  setTonyPosition)
ASSIGN(24,  sendFullscreenMessage)
ASSIGN(25,  saveTonyPosition)
ASSIGN(26,  restoreTonyPosition)
ASSIGN(27,  disableInput)
ASSIGN(28,  enableInput)
ASSIGN(29,  stopTony)

ASSIGN(30,  tonyTakeUp1)
ASSIGN(31,  tonyTakeMid1)
ASSIGN(32,  tonyTakeDown1)
ASSIGN(33,  tonyTakeUp2)
ASSIGN(34,  tonyTakeMid2)
ASSIGN(35,  tonyTakeDown2)

ASSIGN(72,  tonyPutUp1)
ASSIGN(73,  tonyPutMid1)
ASSIGN(74,  tonyPutDown1)
ASSIGN(75,  tonyPutUp2)
ASSIGN(76,  tonyPutMid2)
ASSIGN(77,  tonyPutDown2)

ASSIGN(36,  tonyOnTheFloor)
ASSIGN(37,  tonyGetUp)
ASSIGN(38,  tonyShepherdess)
ASSIGN(39,  tonyWhistle)

ASSIGN(40,  tonyLaugh)
ASSIGN(41,  tonyHips)
ASSIGN(42,  tonySing)
ASSIGN(43,  tonyIndicate)
ASSIGN(44,  tonyScaredWithHands)
ASSIGN(49,  tonyScaredWithoutHands)
ASSIGN(45,  tonyWithGlasses)
ASSIGN(46,  tonyWithWorm)
ASSIGN(47,  tonyWithHammer)
ASSIGN(48,  tonyWithRope)
ASSIGN(90,  tonyWithRabbitANIM)
ASSIGN(91,  tonyWithRecipeANIM)
ASSIGN(92,  tonyWithCardsANIM)
ASSIGN(93,  tonyWithSnowmanANIM)
ASSIGN(94,  tonyWithSnowmanStart)
ASSIGN(95,  tonyWithSnowmanEnd)
ASSIGN(96,  tonyWithRabbitStart)
ASSIGN(97,  tonyWithRabbitEnd)
ASSIGN(98,  tonyWithRecipeStart)
ASSIGN(99,  tonyWithRecipeEnd)
ASSIGN(100, tonyWithCardsStart)
ASSIGN(101, tonyWithCardsEnd)
ASSIGN(102, tonyWithNotebookStart)
ASSIGN(103, tonyWithNotebookEnd)
ASSIGN(104, tonyWithMegaphoneStart)
ASSIGN(105, tonyWithMegaphoneEnd)
ASSIGN(106, tonyWithBeardStart)
ASSIGN(107, tonyWithBeardEnd)
ASSIGN(108, tonyGiggle)
ASSIGN(109, tonyDisgusted)
ASSIGN(110, tonySarcastic)
ASSIGN(111, tonyMacbeth)
ASSIGN(112, tonySniffLeft)
ASSIGN(113, tonySniffRight)
ASSIGN(114, tonyScaredStart)
ASSIGN(115, tonyScaredEnd)
ASSIGN(116, tonyWithSecretary)

ASSIGN(50,  charSetCode)
ASSIGN(51,  charSetColor)
ASSIGN(52,  charSetTalkPattern)
ASSIGN(53,  charSendMessage)
ASSIGN(54,  charSetStartEndTalkPattern)

ASSIGN(60,  mCharSetCode)
ASSIGN(61,  mCharSetColor)
ASSIGN(62,  mCharSetCurrentGroup)
ASSIGN(63,  mCharSetNumTalksInGroup)
ASSIGN(64,  mCharSetNumTexts)
ASSIGN(65,  mCharSendMessage)
ASSIGN(66,  mCharSetPosition)
ASSIGN(67,  mCharSetAlwaysBack)
ASSIGN(68,  mCharResetCode)

ASSIGN(70,  startDialog)
ASSIGN(71,  sendDialogMessage)

ASSIGN(80,  takeOwnership)
ASSIGN(81,  releaseOwnership)

ASSIGN(86,  playSoundEffect)
ASSIGN(87,  playJingle)
ASSIGN(88,  fadeInSoundEffect)
ASSIGN(89,  fadeOutSoundEffect)
ASSIGN(123, fadeInJingle)
ASSIGN(124, fadeOutJingle)
ASSIGN(125, muteSoundEffect)
ASSIGN(126, demuteSoundEffect)
ASSIGN(127, muteJingle)
ASSIGN(128, demuteJingle)
ASSIGN(84,  stopSoundEffect)
ASSIGN(85,  stopJingle)
ASSIGN(83,  playItemSfx)
ASSIGN(129, jingleFadeStart)
ASSIGN(130, jingleFadeEnd)

ASSIGN(120, shakeScreen)
ASSIGN(121, autoSave)
ASSIGN(122, abortGame)
ASSIGN(131, noBullsEye)
ASSIGN(132, sendFullscreenMsgStart)
ASSIGN(133, sendFullscreenMsgEnd)
ASSIGN(134, custEnableGUI)
ASSIGN(135, custDisableGUI)
ASSIGN(136, clearScreen)
ASSIGN(137, patIrqFreeze)
ASSIGN(138, tonySetPerorate)
ASSIGN(139, openInitLoadMenu)
ASSIGN(140, openInitOptions)
ASSIGN(141, syncScrollLocation)
ASSIGN(142, closeLocation)
ASSIGN(143, setAlwaysDisplay)
ASSIGN(144, doCredits)

ASSIGN(200, mustSkipIdleStart);
ASSIGN(201, mustSkipIdleEnd);

END_CUSTOM_FUNCTION_MAP()

void processKilledCallback(Common::PROCESS *p) {
	for (uint i = 0; i < 10; i++) {
		if (GLOBALS._mut[i]._ownerPid == p->pid) {
			// Handle scripts which don't call ReleaseOwnership, such as
			// the one in loc37's vEnter when Tony is chasing the mouse.
			debug(DEBUG_BASIC, "Force-releasing mutex %d after process died", i);

			GLOBALS._mut[i]._ownerPid = 0;
			GLOBALS._mut[i]._lockCount = 0;
			CoroScheduler.setEvent(GLOBALS._mut[i]._eventId);
		}
	}
}

void setupGlobalVars(RMTony *tony, RMPointer *ptr, RMGameBoxes *box, RMLocation *loc, RMInventory *inv, RMInput *input) {
	GLOBALS._tony = tony;
	GLOBALS._pointer = ptr;
	GLOBALS._boxes = box;
	GLOBALS._loc = loc;
	GLOBALS._inventory = inv;
	GLOBALS._input = input;

	GLOBALS.DisableGUI = mainDisableGUI;
	GLOBALS.EnableGUI = mainEnableGUI;

	GLOBALS._bAlwaysDisplay = false;

	CoroScheduler.setResourceCallback(processKilledCallback);
	for (int i = 0; i < 10; i++)
		GLOBALS._mut[i]._eventId = CoroScheduler.createEvent(false, true);

	for (int i = 0; i < 200; i++)
		GLOBALS._ambiance[i] = 0;

	GLOBALS._ambiance[6] =  AMBIANCE_CRICKETS;
	GLOBALS._ambiance[7] =  AMBIANCE_CRICKETS;
	GLOBALS._ambiance[8] =  AMBIANCE_CRICKETSMUFFLED;
	GLOBALS._ambiance[10] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[12] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[13] = AMBIANCE_CRICKETSMUFFLED;
	GLOBALS._ambiance[15] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[16] = AMBIANCE_CRICKETSWIND;
	GLOBALS._ambiance[18] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[19] = AMBIANCE_CRICKETSWIND;
	GLOBALS._ambiance[20] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[23] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[26] = AMBIANCE_SEAHALFVOLUME;
	GLOBALS._ambiance[27] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[28] = AMBIANCE_CRICKETSWIND;
	GLOBALS._ambiance[31] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[33] = AMBIANCE_SEA;
	GLOBALS._ambiance[35] = AMBIANCE_SEA;
	GLOBALS._ambiance[36] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[37] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[40] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[41] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[42] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[45] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[51] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[52] = AMBIANCE_CRICKETSWIND1;
	GLOBALS._ambiance[53] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[54] = AMBIANCE_CRICKETS;
	GLOBALS._ambiance[57] = AMBIANCE_WIND;
	GLOBALS._ambiance[58] = AMBIANCE_WIND;
	GLOBALS._ambiance[60] = AMBIANCE_WIND;

	// Create an event for the idle skipping
	GLOBALS._hSkipIdle = CoroScheduler.createEvent(true, false);
}

} // end of namespace Tony
