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

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

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-1-1-1/engines/tinsel/tinlib.cpp $
 * $Id: tinlib.cpp 48363 2010-03-22 23:05:37Z fingolfin $
 *
 * Glitter library functions.
 *
 * In the main called only from PCODE.C
 * Function names are the same as Glitter code function names.
 *
 * To ensure exclusive use of resources and exclusive control responsibilities.
 */

#define BODGE

#include "tinsel/actors.h"
#include "tinsel/background.h"
#include "tinsel/bmv.h"
#include "tinsel/config.h"
#include "tinsel/coroutine.h"
#include "tinsel/cursor.h"
#include "tinsel/drives.h"
#include "tinsel/dw.h"
#include "tinsel/events.h"
#include "tinsel/faders.h"
#include "tinsel/film.h"
#include "tinsel/font.h"
#include "tinsel/graphics.h"
#include "tinsel/handle.h"
#include "tinsel/dialogs.h"
#include "tinsel/mareels.h"
#include "tinsel/move.h"
#include "tinsel/multiobj.h"
#include "tinsel/music.h"
#include "tinsel/object.h"
#include "tinsel/palette.h"
#include "tinsel/pcode.h"
#include "tinsel/pid.h"
#include "tinsel/play.h"
#include "tinsel/polygons.h"
#include "tinsel/rince.h"
#include "tinsel/savescn.h"
#include "tinsel/sched.h"
#include "tinsel/scn.h"
#include "tinsel/scroll.h"
#include "tinsel/sound.h"
#include "tinsel/strres.h"
#include "tinsel/sysvar.h"
#include "tinsel/text.h"
#include "tinsel/timers.h"		// For ONE_SECOND constant
#include "tinsel/tinlib.h"
#include "tinsel/tinsel.h"
#include "tinsel/token.h"


namespace Tinsel {

//----------------- EXTERNAL GLOBAL DATA --------------------

// In DOS_DW.C
extern bool bRestart;		// restart flag - set to restart the game
extern bool bHasRestarted;	// Set after a restart

// In PCODE.CPP
extern bool bNoPause;

// In DOS_MAIN.C
// TODO/FIXME: From dos_main.c: "Only used on PSX so far"
int clRunMode = 0;

//----------------- EXTERNAL FUNCTIONS ---------------------

// in BG.CPP
extern void ChangePalette(SCNHANDLE hPal);

// in PDISPLAY.CPP
extern void EnableTags();
extern void DisableTags();
bool DisableTagsIfEnabled();
extern void setshowstring();

// in SAVELOAD.CPP
extern int NewestSavedGame();

// in SCENE.CPP
extern void setshowpos();
extern int sceneCtr;

// in TINSEL.CPP
extern void SetCdChangeScene(SCNHANDLE hScene);
extern void SetHookScene(SCNHANDLE scene, int entrance, int transition);
extern void SetNewScene(SCNHANDLE scene, int entrance, int transition);
extern void UnHookScene();
extern void SuspendHook();
extern void UnSuspendHook();

#ifdef BODGE
// In HANDLE.CPP
bool ValidHandle(SCNHANDLE offset);

// In SCENE.CPP
SCNHANDLE GetSceneHandle();
#endif

//----------------- GLOBAL GLOBAL DATA --------------------

bool bEnableMenu;

static bool bInstantScroll = false;
static bool bEscapedCdPlay = false;


//----------------- LOCAL DEFINES --------------------

#define JAP_TEXT_TIME	(2*ONE_SECOND)

/*----------------------------------------------------------------------*\
|*                      Library Procedure and Function codes            *|
\*----------------------------------------------------------------------*/

enum MASTER_LIB_CODES {
	ACTORATTR, ACTORBRIGHTNESS, ACTORDIRECTION, ACTORPALETTE, ACTORPRIORITY, ACTORREF,
	ACTORRGB, ACTORSCALE, ACTORSON, ACTORXPOS, ACTORYPOS, ADDHIGHLIGHT,
	ADDINV, ADDINV1, ADDINV2, ADDOPENINV, ADDTOPIC, AUXSCALE, BACKGROUND, BLOCKING,
	CALLACTOR, CALLGLOBALPROCESS, CALLOBJECT, CALLPROCESS, CALLSCENE, CALLTAG,
	CAMERA, CDCHANGESCENE, CDDOCHANGE, CDENDACTOR, CDLOAD, CDPLAY, CLEARHOOKSCENE,
	CLOSEINVENTORY, CONTROL, CONVERSATION, CONVTOPIC, CURSOR, CURSORXPOS, CURSORYPOS,
	CUTSCENE, DECCONVW, DECCSTRINGS, DECCURSOR, DECFLAGS, DECINV1, DECINV2, DECINVW,
	DECLARELANGUAGE, DECLEAD, DECSCALE, DECTAGFONT, DECTALKFONT, DELICON,
	DELINV, DELTOPIC, DIMMUSIC, DROP, DROPEVERYTHING, DROPOUT, EFFECTACTOR, ENABLEMENU,
	ENDACTOR, ESCAPE, ESCAPEOFF, ESCAPEON, EVENT, FACETAG, FADEIN, FADEMIDI,
	FADEOUT, FRAMEGRAB, FREEZECURSOR, GETINVLIMIT, GHOST, GLOBALVAR, GRABMOVIE, HAILSCENE,
	HASRESTARTED, HAVE, HELDOBJECT, HIDEACTOR, HIDEBLOCK, HIDEEFFECT, HIDEPATH,
	HIDEREFER, HIDETAG, HOLD, HOOKSCENE, IDLETIME, ININVENTORY, INSTANTSCROLL, INVDEPICT,
	INVENTORY, INVPLAY, INWHICHINV, KILLACTOR, KILLBLOCK, KILLEXIT, KILLGLOBALPROCESS,
	KILLPROCESS, KILLTAG, LOCALVAR, MOVECURSOR, MOVETAG, MOVETAGTO, NEWSCENE,
	NOBLOCKING, NOPAUSE, NOSCROLL, OBJECTHELD, OFFSET, OTHEROBJECT, PAUSE, PLAY, PLAYMIDI,
	PLAYMOVIE, PLAYMUSIC, PLAYRTF, PLAYSAMPLE, POINTACTOR, POINTTAG, POSTACTOR, POSTGLOBALPROCESS,
	POSTOBJECT, POSTPROCESS, POSTTAG, PREPARESCENE, PRINT, PRINTCURSOR, PRINTOBJ, PRINTTAG,
	QUITGAME, RANDOM, RESETIDLETIME, RESTARTGAME, RESTORESCENE, RESTORE_CUT,
	RESUMELASTGAME, RUNMODE, SAMPLEPLAYING, SAVESCENE, SAY, SAYAT, SCALINGREELS,
	SCANICON, SCREENXPOS, SCREENYPOS, SCROLL, SCROLLPARAMETERS, SENDACTOR, SENDGLOBALPROCESS,
	SENDOBJECT, SENDPROCESS, SENDTAG, SETACTOR, SETBLOCK, SETBRIGHTNESS, SETEXIT, SETINVLIMIT,
	SETINVSIZE, SETLANGUAGE, SETPALETTE, SETSYSTEMREEL, SETSYSTEMSTRING, SETSYSTEMVAR,
	SETTAG, SETTIMER, SHELL, SHOWACTOR, SHOWBLOCK, SHOWEFFECT, SHOWMENU, SHOWPATH,
	SHOWPOS, SHOWREFER, SHOWSTRING, SHOWTAG, SPLAY, STAND, STANDTAG, STARTGLOBALPROCESS,
	STARTPROCESS, STARTTIMER, STOPMIDI, STOPSAMPLE, STOPWALK, SUBTITLES, SWALK, SWALKZ,
	SYSTEMVAR, TAGACTOR, TAGTAGXPOS, TAGTAGYPOS, TAGWALKXPOS, TAGWALKYPOS, TALK, TALKAT,
	TALKATS, TALKATTR, TALKPALETTEINDEX, TALKRGB, TALKVIA, TEMPTAGFONT, TEMPTALKFONT,
	THISOBJECT, THISTAG, TIMER, TOPIC, TOPPLAY, TOPWINDOW, TRANSLUCENTINDEX,
	TRYPLAYSAMPLE, UNDIMMUSIC, UNHOOKSCENE, UNTAGACTOR, VIBRATE, WAITFRAME, WAITKEY,
	WAITSCROLL, WAITTIME, WALK, WALKED, WALKEDPOLY, WALKEDTAG, WALKINGACTOR, WALKPOLY,
	WALKTAG, WALKXPOS, WALKYPOS, WHICHCD, WHICHINVENTORY, ZZZZZZ,
	HIGHEST_LIBCODE
};

const MASTER_LIB_CODES DW1DEMO_CODES[] = {
	ACTORREF, ACTORXPOS, ACTORYPOS, ADDTOPIC, ADDINV1, ADDINV2, AUXSCALE, BACKGROUND,
	CAMERA, CONTROL, CONVERSATION, CONVTOPIC, HIGHEST_LIBCODE, CURSORXPOS, CURSORYPOS,
	DECCONVW, DECCURSOR, DECTAGFONT, DECINVW, DECINV1, DECINV2, DECLEAD, DELICON,
	DELINV, EVENT, HIGHEST_LIBCODE, HELDOBJECT, HIDEACTOR, ININVENTORY, HIGHEST_LIBCODE,
	INVENTORY, HIGHEST_LIBCODE, KILLACTOR, KILLBLOCK, KILLTAG, SCREENXPOS,
	HIGHEST_LIBCODE, MOVECURSOR, NEWSCENE, NOSCROLL, OBJECTHELD, OFFSET, HIGHEST_LIBCODE,
	PLAY, PLAYSAMPLE, PREPARESCENE, PRINT, PRINTOBJ, PRINTTAG, RESTORESCENE, SAVESCENE,
	SCANICON, SCROLL, SETACTOR, SETBLOCK, HIGHEST_LIBCODE, SETTAG, SETTIMER, SHOWPOS,
	SPLAY, STAND, STANDTAG, STOPWALK, HIGHEST_LIBCODE, SWALK, TAGACTOR, TALK,
	SCREENYPOS, UNTAGACTOR, VIBRATE, WAITKEY, WAITTIME, WALK, WALKINGACTOR, WALKPOLY,
	WALKTAG, RANDOM, TIMER
};

const MASTER_LIB_CODES DW1_CODES[] = {
	ACTORATTR, ACTORDIRECTION, ACTORREF, ACTORSCALE, ACTORXPOS,
	ACTORYPOS, ADDTOPIC, ADDINV1, ADDINV2, ADDOPENINV, AUXSCALE,
	BACKGROUND, CAMERA, CLOSEINVENTORY, CONTROL, CONVERSATION,
	CONVTOPIC, CURSORXPOS, CURSORYPOS, DECCONVW, DECCURSOR,
	DECINV1, DECINV2, DECINVW, DECLEAD, DECTAGFONT,
	DECTALKFONT, DELICON, DELINV, EFFECTACTOR, ESCAPE, EVENT,
	GETINVLIMIT, HELDOBJECT, HIDEACTOR, ININVENTORY, INVDEPICT,
	INVENTORY, KILLACTOR, KILLBLOCK, KILLEXIT, KILLTAG, SCREENXPOS,
	MOVECURSOR, NEWSCENE, NOSCROLL, OBJECTHELD, OFFSET, PAUSE,
	PLAY, PLAYMIDI, PLAYSAMPLE, PREPARESCENE, PRINT, PRINTOBJ,
	PRINTTAG, RANDOM, RESTORESCENE, SAVESCENE, SCALINGREELS,
	SCANICON, SCROLL, SETACTOR, SETBLOCK, SETEXIT, SETINVLIMIT,
	SETPALETTE, SETTAG, SETTIMER, SHOWPOS, SHOWSTRING, SPLAY,
	STAND, STANDTAG, STOPWALK, SWALK, TAGACTOR, TALK, TALKATTR, TIMER,
	SCREENYPOS, TOPPLAY, TOPWINDOW, UNTAGACTOR, VIBRATE, WAITKEY,
	WAITTIME, WALK, WALKED, WALKINGACTOR, WALKPOLY, WALKTAG,
	WHICHINVENTORY, ACTORSON, CUTSCENE, HOOKSCENE, IDLETIME,
	RESETIDLETIME, TALKAT, UNHOOKSCENE, WAITFRAME,	DECCSTRINGS,
	STOPMIDI, STOPSAMPLE, TALKATS, DECFLAGS, FADEMIDI,
	CLEARHOOKSCENE, SETINVSIZE, INWHICHINV, NOBLOCKING,
	SAMPLEPLAYING, TRYPLAYSAMPLE, ENABLEMENU, RESTARTGAME, QUITGAME,
	FRAMEGRAB, PLAYRTF, CDPLAY, CDLOAD, HASRESTARTED, RESTORE_CUT,
	RUNMODE, SUBTITLES, SETLANGUAGE,
	HIGHEST_LIBCODE
};

const MASTER_LIB_CODES DW2_CODES[] = {
	ACTORBRIGHTNESS, ACTORDIRECTION, ACTORPALETTE, ACTORPRIORITY,
	ACTORREF, ACTORRGB, ACTORSCALE, ACTORXPOS, ACTORYPOS,
	ADDHIGHLIGHT, ADDINV, ADDINV1, ADDINV2, ADDOPENINV, ADDTOPIC,
	BACKGROUND, CALLACTOR, CALLGLOBALPROCESS, CALLOBJECT,
	CALLPROCESS, CALLSCENE, CALLTAG, CAMERA, CDCHANGESCENE,
	CDDOCHANGE, CDLOAD, CDPLAY, CLEARHOOKSCENE, CLOSEINVENTORY,
	CONTROL, CONVERSATION, CURSOR, CURSORXPOS, CURSORYPOS,
	DECCONVW, DECCURSOR, DECFLAGS, DECINV1, DECINV2, DECINVW,
	DECLEAD, DECSCALE, DECTAGFONT, DECTALKFONT, DELTOPIC,
	DIMMUSIC, DROP, DROPOUT, EFFECTACTOR, ENABLEMENU, ENDACTOR,
	ESCAPEOFF, ESCAPEON, EVENT, FACETAG, FADEIN, FADEOUT, FRAMEGRAB,
	FREEZECURSOR, GETINVLIMIT, GHOST, GLOBALVAR, GRABMOVIE,
	HASRESTARTED, HAVE, HELDOBJECT, HIDEACTOR, HIDEBLOCK, HIDEEFFECT,
	HIDEPATH, HIDEREFER, HIDETAG, HOLD, HOOKSCENE, IDLETIME,
	INSTANTSCROLL, INVENTORY, INVPLAY, INWHICHINV, KILLACTOR,
	KILLGLOBALPROCESS, KILLPROCESS, LOCALVAR, MOVECURSOR, MOVETAG,
	MOVETAGTO, NEWSCENE, NOBLOCKING, NOPAUSE, NOSCROLL, OFFSET,
	OTHEROBJECT, PAUSE, PLAY, PLAYMUSIC, PLAYRTF, PLAYSAMPLE,
	POINTACTOR, POINTTAG, POSTACTOR, POSTGLOBALPROCESS, POSTOBJECT,
	POSTPROCESS, POSTTAG, PRINT, PRINTCURSOR, PRINTOBJ, PRINTTAG,
	QUITGAME, RANDOM, RESETIDLETIME, RESTARTGAME, RESTORESCENE,
	RUNMODE, SAVESCENE, SAY, SAYAT, SCALINGREELS, SCREENXPOS,
	SCREENYPOS, SCROLL, SCROLLPARAMETERS, SENDACTOR, SENDGLOBALPROCESS,
	SENDOBJECT, SENDPROCESS, SENDTAG, SETBRIGHTNESS, SETINVLIMIT,
	SETINVSIZE, SETLANGUAGE, SETPALETTE, SETSYSTEMSTRING, SETSYSTEMVAR,
	SHELL, SHOWACTOR, SHOWBLOCK, SHOWEFFECT, SHOWPATH, SHOWREFER,
	SHOWTAG, STAND, STANDTAG, STARTGLOBALPROCESS, STARTPROCESS,
	STARTTIMER, STOPWALK, SUBTITLES, SWALK, SYSTEMVAR, TAGTAGXPOS,
	TAGTAGYPOS, TAGWALKXPOS, TAGWALKYPOS, TALK, TALKAT, TALKPALETTEINDEX,
	TALKRGB, TALKVIA, THISOBJECT, THISTAG, TIMER, TOPIC, TOPPLAY,
	TOPWINDOW, TRANSLUCENTINDEX, UNDIMMUSIC, UNHOOKSCENE, WAITFRAME,
	WAITKEY, WAITSCROLL, WAITTIME, WALK, WALKED, WALKEDPOLY, WALKEDTAG,
	WALKINGACTOR, WALKPOLY, WALKTAG, WALKXPOS, WALKYPOS, WHICHCD,
	WHICHINVENTORY, ZZZZZZ, SWALKZ, DROPEVERYTHING, BLOCKING, STOPSAMPLE,
	CDENDACTOR, DECLARELANGUAGE, RESUMELASTGAME, SHOWMENU, TEMPTALKFONT,
	TEMPTAGFONT, PLAYMOVIE, HAILSCENE, SETSYSTEMREEL,
	HIGHEST_LIBCODE
};

//----------------- LOCAL GLOBAL DATA --------------------

// Saved cursor co-ordinates for control(on) to restore cursor position
// as it was at control(off).
// They are global so that MoveCursor(..) has a net effect if it
// precedes control(on).
static int controlX = 0, controlY = 0;

static int offtype = 0;			// used by Control()
static uint32 lastValue = 0;	// used by RandomFn()
static int scrollNumber = 0;	// used by scroll()

static bool bNotPointedRunning = false;	// Used in Printobj and PrintObjPointed

static COLORREF s_talkfontColor = 0;

//----------------- FORWARD REFERENCES --------------------

static int HeldObject();
static void PostTag(CORO_PARAM, int tagno, TINSEL_EVENT event, HPOLYGON hp, int myEscape);
void ResetIdleTime();
static void SendTag(CORO_PARAM, int tagno, TINSEL_EVENT event, HPOLYGON hp, int myEscape, bool *result);
static void StandTag(int actor, HPOLYGON hp);
void StopMidiFn();
void StopSample(int sample = -1);
static void StopWalk(int actor);
static void WaitScroll(CORO_PARAM, int myescEvent);
void Walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool igPath,
		  int zOverride, bool escOn, int myescTime);

//----------------- SUPPORT FUNCTIONS --------------------

/**
 * For Scroll() and Offset(), work out top left for a
 * given screen position.
 */
static void DecodeExtreme(EXTREME extreme, int *px, int *py) {
	int	Loffset, Toffset;

	PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);

	switch (extreme) {
	case EX_BOTTOM:
		*px = Loffset;
		*py = BgHeight() - SCREEN_HEIGHT;
		break;
	case EX_BOTTOMLEFT:
		*px = 0;
		*py = BgHeight() - SCREEN_HEIGHT;
		break;
	case EX_BOTTOMRIGHT:
		*px = BgWidth() - SCREEN_WIDTH;
		*py = BgHeight() - SCREEN_HEIGHT;
		break;
	case EX_LEFT:
		*px = 0;
		*py = Toffset;
		break;
	case EX_RIGHT:
		*px = BgWidth() - SCREEN_WIDTH;
		*py = Toffset;
		break;
	case EX_TOP:
		*px = Loffset;
		*py = 0;
		break;
	case EX_TOPLEFT:
		*px = *py = 0;
		break;
	case EX_TOPRIGHT:
		*px = BgWidth() - SCREEN_WIDTH;
		*py = 0;
		break;
	default:
		break;
	}
}

static void KillSelf(CORO_PARAM) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	CORO_KILL_SELF();

	CORO_END_CODE;
}

struct SCROLL_MONITOR {
	int	x;
	int	y;
	int	thisScroll;
	int	myEscape;
};
typedef SCROLL_MONITOR *PSCROLL_MONITOR;

/**
 * Monitor a scrolling, allowing Escape to interrupt it
 */
static void ScrollMonitorProcess(CORO_PARAM, const void *param) {
	int		Loffset, Toffset;
	const SCROLL_MONITOR *psm = (const SCROLL_MONITOR *)param;

	// COROUTINE
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	do {
		CORO_SLEEP(1);

		// give up if have been superseded
		if (psm->thisScroll != scrollNumber)
			break;

		// If ESCAPE is pressed...
		if (psm->myEscape != GetEscEvents()) {
			// Instant completion!
			Offset(EX_USEXY, psm->x, psm->y);
			break;
		}

		PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);

	} while (Loffset != psm->x || Toffset != psm->y);

	CORO_END_CODE;
}

/**
 * NOT A LIBRARY FUNCTION
 *
 * Poke supplied colour into the DAC queue.
 */
void SetTextPal(COLORREF col) {
	s_talkfontColor = col;
	SetTalkColourRef(col);
	UpdateDACqueue(TalkColour(), 1, &s_talkfontColor);
}

/**
 * Work out a time depending on length of string and
 * subtitle speed modification.
 */
static int TextTime(char *pTstring) {
	if (isJapanMode())
		return JAP_TEXT_TIME;
	else if (!_vm->_config->_textSpeed)
		return strlen(pTstring) + ONE_SECOND;
	else
		return strlen(pTstring) + ONE_SECOND + (_vm->_config->_textSpeed * 5 * ONE_SECOND) / 100;
}

/**
 * KeepOnScreen
 */
void KeepOnScreen(POBJECT pText, int *pTextX, int *pTextY) {
	int	shift;

	// Not off the left
	shift = MultiLeftmost(pText);
	if (shift < 0) {
		MultiMoveRelXY(pText, - shift, 0);
		*pTextX -= shift;
	}

	// Not off the right
	shift = MultiRightmost(pText);
	if (shift > SCREEN_WIDTH) {
		MultiMoveRelXY(pText, SCREEN_WIDTH - shift, 0);
		*pTextX += SCREEN_WIDTH - shift;
	}

	// Not off the top
	shift = MultiHighest(pText);
	if (shift < 0) {
		MultiMoveRelXY(pText, 0, - shift);
		*pTextY -= shift;
	}

	// Not off the bottom
	shift = MultiLowest(pText);
	if (shift > SCREEN_BOX_HEIGHT2) {
		MultiMoveRelXY(pText, 0, SCREEN_BOX_HEIGHT2 - shift);
		*pTextX += SCREEN_WIDTH - shift;
	}
}

/**
 * Waits until the specified process is finished
 */
static void FinishWaiting(CORO_PARAM, const INT_CONTEXT *pic, bool *result = NULL) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	while (pic->resumeCode == RES_WAITING)
		CORO_SLEEP(1);

	if (result)
		*result = pic->resumeCode == RES_FINISHED;
	CORO_END_CODE;
}

void TinGetVersion(WHICH_VER which, char *buffer, int length) {

	if (length > VER_LEN)
		length = VER_LEN;

	char *cptr = (char *)FindChunk(MASTER_SCNHANDLE, CHUNK_TIME_STAMPS);

	switch (which)	{
	case VER_GLITTER:
		memcpy(buffer, cptr, length);
		break;

	case VER_COMPILE:
		memcpy(buffer, cptr + VER_LEN, length);
		break;
	}
}

/********************************************************************\
|*****			Library functions								*****|
\********************************************************************/

/**
 * Set actor's attributes.
 * - currently only the text colour.
 */
static void ActorAttr(int actor, int r1, int g1, int b1) {
	storeActorAttr(actor, r1, g1, b1);
}

/**
 * Behave as if actor has walked into a polygon with given brughtness.
 */
void ActorBrightness(int actor, int brightness) {
	PMOVER pMover = GetMover(actor);

	assert(pMover != NULL);
	assert(brightness >= 0 && brightness <= 10);

	MoverBrightness(pMover, brightness);
}

/**
 * Return a moving actor's current direction.
 */
static int ActorDirection(int actor) {
	PMOVER pMover = GetMover(actor);
	assert(pMover);

	return (int)GetMoverDirection(pMover);
}

/**
 * Set actor's palette details for path brightnesses
 */
void ActorPalette(int actor, int startColour, int length) {
	PMOVER pMover = GetMover(actor);
	assert(pMover);

	StoreMoverPalette(pMover, startColour, length);
}

/**
 * Set actor's Z-factor.
 */
static void ActorPriority(int actor, int zFactor) {
	SetActorZfactor(actor, zFactor);
}

/**
 * Set actor's text colour.
 */
static void ActorRGB(int actor, COLORREF colour) {
	SetActorRGB(actor, colour);
}

/**
 * Return the actor's scale.
 */
static int ActorScale(int actor) {
	PMOVER pMover = GetMover(actor);
	assert(pMover);

	return (int)GetMoverScale(pMover);
}

/**
 * Returns the x or y position of an actor.
 */
static int ActorPos(int xory, int actor) {
	int x, y;

	GetActorPos(actor, &x, &y);
	return (xory == ACTORXPOS) ? x : y;
}

/**
 * Make all actors alive at the start of each scene.
 */
static void ActorsOn() {
	setactorson();
}

/**
 * Adds an icon to the conversation window.
 */
static void AddTopic(int icon) {
	AddToInventory(INV_CONV, icon, false);
}

/**
 * Place the object in inventory 1 or 2.
 */
static void AddInv(int invno, int object) {
	// illegal inventory number
	assert(invno == INV_1 || invno == INV_2 || invno == INV_OPEN || invno == INV_DEFAULT);

	AddToInventory(invno, object, false);
}

/**
 * Define an actor's walk and stand reels for an auxilliary scale.
 */
static void AuxScale(int actor, int scale, SCNHANDLE *rp) {
	PMOVER pMover = GetMover(actor);
	assert(pMover);

	int j;
	for (j = 0; j < 4; ++j)
		pMover->walkReels[scale-1][j] = *rp++;
	for (j = 0; j < 4; ++j)
		pMover->standReels[scale-1][j] = *rp++;
	for (j = 0; j < 4; ++j)
		pMover->talkReels[scale-1][j] = *rp++;
}

/**
 * Defines the background image for a scene.
 */
static void Background(CORO_PARAM, SCNHANDLE bfilm) {
	StartupBackground(coroParam, bfilm);
}

/**
 * Disable dynamic blocking for current scene.
 */
void Blocking(bool onOrOff) {
	SetSysVar(ISV_NO_BLOCKING, !onOrOff);
}

/**
 * Sets focus of the scroll process.
 */
static void Camera(int actor) {
	ScrollFocus(actor);
}

/**
 * Sets the CD Change Scene
 */

static void CdChangeScene(SCNHANDLE hScene) {
	SetCdChangeScene(hScene);
}

/**
 * CdDoChange
 */
void CdDoChange(CORO_PARAM) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	if (!GotoCD())
		return;

	CORO_INVOKE_0(CdCD);

	CdHasChanged();

	CORO_END_CODE;
}

/**
 * CdEndActor("actor")
 */
void CdEndActor(int	actor, int	myEscape) {
	PMOVER	pMover;			// for if it's a moving actor

	// Only do it if escaped!
	if (myEscape && myEscape != GetEscEvents()) {
		// End current graphic
		dwEndActor(actor);

		// un-hide movers
		pMover = GetMover(actor);
		if (pMover)
			UnHideMover(pMover);
	}
}

/**
 * A CDPLAY() is imminent.
 */
static void CDload(SCNHANDLE start, SCNHANDLE next, int myEscape) {
	assert(start && next && start != next); // cdload() fault

	if (TinselV2) {
		if (myEscape && myEscape != GetEscEvents()) {
			bEscapedCdPlay = true;
			return;
		}

		LoadExtraGraphData(start, next);
	}
}

/**
 * Clear the hooked scene (if any)
 */
static void ClearHookScene() {
	SetHookScene(0, 0, TRANS_DEF);
}

/**
 * Guess what.
 */
static void CloseInventory() {
	KillInventory();
}

/**
 * Turn off cursor and take control from player - and variations on the	 theme.
 *  OR Restore cursor and return control to the player.
 */
void Control(int param) {
	if (TinselV2) {
		if (param)
			ControlOn();
		else {
			ControlOff();

			switch (WhichInventoryOpen()) {
			case INV_1:
			case INV_2:
			case INV_MENU:
				KillInventory();
				break;
			default:
				break;
			}
		}

		return;
	}

	// Tinsel 1 handling code
	bEnableMenu = false;

	switch (param) {
	case CONTROL_STARTOFF:
		GetControlToken();	// Take control
		DisableTags();			// Switch off tags
		DwHideCursor();			// Blank out cursor
		offtype = param;
		break;

	case CONTROL_OFF:
	case CONTROL_OFFV:
	case CONTROL_OFFV2:
		if (TestToken(TOKEN_CONTROL)) {
			GetControlToken();	// Take control

			DisableTags();			// Switch off tags
			GetCursorXYNoWait(&controlX, &controlY, true);	// Store cursor position

			// There may be a button timing out
			GetToken(TOKEN_LEFT_BUT);
			FreeToken(TOKEN_LEFT_BUT);
		}

		if (offtype == CONTROL_STARTOFF)
			GetCursorXYNoWait(&controlX, &controlY, true);	// Store cursor position

		offtype = param;

		if (param == CONTROL_OFF)
			DwHideCursor();		// Blank out cursor
		else if (param == CONTROL_OFFV) {
			UnHideCursor();
			FreezeCursor();
		} else if (param == CONTROL_OFFV2) {
			UnHideCursor();
		}
		break;

	case CONTROL_ON:
		if (offtype != CONTROL_OFFV2 && offtype != CONTROL_STARTOFF)
			SetCursorXY(controlX, controlY);// ... where it was

		FreeControlToken();	// Release control

		if (!InventoryActive())
			EnableTags();		// Tags back on

		RestoreMainCursor();		// Re-instate cursor...
	}
}

/**
 * Open or close the conversation window.
 */
static void Conversation(CORO_PARAM, int fn, HPOLYGON hp, int actor, bool escOn, int myEscape) {
	assert(hp != NOPOLY); // conversation() must (currently) be called from a polygon code block
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	if (fn == CONV_END) {
		// Close down conversation
		CloseDownConv();
	} else if ((fn == CONV_TOP) || (fn == CONV_DEF) || (fn == CONV_BOTTOM)) {
		// TOP of screen, Default (i.e. TOP of screen), or BOTTOM of screen

		// If waiting is enabled, wait for ongoing scroll
		if (TinselV2 && SysVar(SV_CONVERSATIONWAITS))
			CORO_INVOKE_1(WaitScroll, myEscape);

		// Don't do it if it's not wanted
		if (escOn && myEscape != GetEscEvents())
			return;

		// Don't do it if already in a conversation
		if (IsConvWindow())
			return;

		KillInventory();

		if (TinselV2) {
			// If this is from a tag polygon, get the associated
			// actor (the one the polygon is named after), if any.
			if (!actor) {
				actor = GetTagPolyId(hp);
				if (actor & ACTORTAG_KEY)
					actor &= ~ACTORTAG_KEY;
				else
					actor = 0;
			}

			// Top or bottom; tag polygon or tagged actor
			SetConvDetails((CONV_PARAM)fn, hp, actor);
		} else {
			convPos(fn);
			ConvPoly(hp);
		}

		PopUpInventory(INV_CONV);	// Conversation window
		ConvAction(INV_OPENICON);	// CONVERSATION event
	}

	CORO_END_CODE;
}

/**
 * Add icon to conversation window's permanent default list.
 */
static void ConvTopic(int icon) {
	PermaConvIcon(icon);
}

/**
 * Cursor(on/off)
 */
void Cursor(int onoff) {
	if (onoff) {
		// Re-instate cursor
		UnHideCursor();
	} else {
		// Blank out cursor
		DwHideCursor();
	}
}

/**
 * Returns the x or y position of the cursor.
 */
static int CursorPos(int xory) {
	int x, y;

	GetCursorXY(&x, &y, true);
	return (xory == CURSORXPOS) ? x : y;
}

/**
 * Declare conversation window.
 */
static void DecConvW(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight,
			int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) {
	idec_convw(text, MaxContents, MinWidth, MinHeight,
			StartWidth, StartHeight, MaxWidth, MaxHeight);
}

/**
 * Declare config strings.
 */
static void DecCStrings(SCNHANDLE *tp) {
	setConfigStrings(tp);
}

/**
 * Declare cursor's reels.
 */
static void DecCursor(SCNHANDLE hFilm) {
	DwInitCursor(hFilm);
}

/**
 * Declare the language flags.
 */
static void DecFlags(SCNHANDLE hFilm) {
	setFlagFilms(hFilm);
}

/**
 * Declare inventory 1's parameters.
 */
static void DecInv1(SCNHANDLE text, int MaxContents,
		int MinWidth, int MinHeight,
		int StartWidth, int StartHeight,
		int MaxWidth, int MaxHeight) {
	idec_inv1(text, MaxContents, MinWidth, MinHeight,
			StartWidth, StartHeight, MaxWidth, MaxHeight);
}

/**
 * Declare inventory 2's parameters.
 */
static void DecInv2(SCNHANDLE text, int MaxContents,
		int MinWidth, int MinHeight,
		int StartWidth, int StartHeight,
		int MaxWidth, int MaxHeight) {
	idec_inv2(text, MaxContents, MinWidth, MinHeight,
			StartWidth, StartHeight, MaxWidth, MaxHeight);
}

/**
 * Declare the bits that the inventory windows are constructed from.
 */
static void DecInvW(SCNHANDLE hf) {
	setInvWinParts(hf);
}

/**
 * DeclareLanguage
 */
static void DeclareLanguage(int languageId, SCNHANDLE hDescription, SCNHANDLE hFlagFilm) {
	LanguageFacts(languageId, hDescription, hFlagFilm);
}

/**
 * Declare lead actor.
 * @param id		Actor Id
 * @param rp		Walk and stand reels for all the regular scales (v1 only)
 * @param text		Tag text (v1 only)
 */
static void DecLead(uint32 id, SCNHANDLE *rp = 0, SCNHANDLE text = 0) {
	PMOVER	pMover;		// Moving actor structure

	if (TinselV2) {
		// Tinsel 2 only specifies the lead actor Id
		SetLeadId(id);
		RegisterMover(id);

	} else {

		Tag_Actor(id, text, TAG_DEF);	// The lead actor is automatically tagged
		SetLeadId(id);			// Establish this as the lead
		RegisterMover(id);			// Establish as a moving actor

		pMover = GetMover(id);		// Get moving actor structure
		assert(pMover);

		// Store all those reels
		int i, j;
		for (i = 0; i < 5; ++i) {
			for (j = 0; j < 4; ++j)
				pMover->walkReels[i][j] = *rp++;
			for (j = 0; j < 4; ++j)
				pMover->standReels[i][j] = *rp++;
			for (j = 0; j < 4; ++j)
				pMover->talkReels[i][j] = *rp++;
		}


		for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) {
			for (j = 0; j < 4; ++j) {
				pMover->walkReels[i][j] = pMover->walkReels[4][j];
				pMover->standReels[i][j] = pMover->standReels[2][j];
				pMover->talkReels[i][j] = pMover->talkReels[4][j];
			}
		}
	}
}

/**
 * DecScale("actor", scale, 12*"reel")
 * Define an actor's walk and stand reels for a scale.
 */
static void DecScale(int actor, int scale,
		SCNHANDLE wkl, SCNHANDLE wkr, SCNHANDLE wkf, SCNHANDLE wka,
		SCNHANDLE stl, SCNHANDLE str, SCNHANDLE stf, SCNHANDLE sta,
		SCNHANDLE tal, SCNHANDLE tar, SCNHANDLE taf, SCNHANDLE taa) {
	PMOVER pMover = GetMover(actor);
	assert(pMover);

	SetWalkReels(pMover, scale, wkl, wkr, wkf, wka);
	SetStandReels(pMover, scale, stl, str, stf, sta);
	SetTalkReels(pMover, scale, tal, tar, taf, taa);
}

/**
 * Declare the text font.
 */
static void DecTagFont(SCNHANDLE hf) {
	SetTagFontHandle(hf);		// Store the font handle
	if (TinselV0)
		SetTalkFontHandle(hf);	// Also re-use for talk text
}

/**
 * Declare the text font.
 */
static void DecTalkFont(SCNHANDLE hf) {
	SetTalkFontHandle(hf);		// Store the font handle
}

/**
 * Remove an icon from the conversation window.
 */
static void DelIcon(int icon) {
	RemFromInventory(INV_CONV, icon);
}

/**
 * Delete the object from inventory 1 or 2.
 */
static void DelInv(int object) {
	if (!RemFromInventory(INV_1, object))		// Remove from inventory 1...
		RemFromInventory(INV_2, object);		// ...or 2 (whichever)

	DropItem(object);			// Stop holding it
}

/**
 * DelTopic
 */
static void DelTopic(int icon) {
	RemFromInventory(INV_CONV, icon);
}

/**
 * DimMusic
 */
static void DimMusic() {
	_vm->_pcmMusic->dim(true);
}

/**
 * Delete the object from inventory 1 or 2.
 */
static void Drop(int object) {
	if (object == -1)
		object = HeldObject();

	if (!RemFromInventory(INV_1, object))	// Remove from inventory 1...
		RemFromInventory(INV_2, object);	// ...or 2 (whichever)

	DropItem(object);			// Stop holding it
}

/**
 * Delete all objects from inventory 1 and 2.
 */
static void DropEverything() {
	HoldItem(NOOBJECT, false);

	ClearInventory(INV_1);
	ClearInventory(INV_2);
}

/**
 * EnableMenu
 */
static void EnableMenu() {
	bEnableMenu = true;
}

/**
 * Kill an actor's current graphics.
 */
static void EndActor(int actor) {
	dwEndActor(actor);
}

/**
 * Get the actor to look at the polygon.
 * If the actor is at the tag, do a StandTag().
 */
static void FaceTag(int actor, HPOLYGON hp) {
	PMOVER	pMover;		// Moving actor structure
	int	nowx, nowy;
	int	nodex, nodey;

	assert(hp != NOPOLY);

	/*
	 * Get which moving actor it is
	 */
	pMover = GetMover(actor);
	assert(pMover);
	if (MoverHidden(pMover))
		return;

	/*
	 * Stop the actor
	 */
	StopWalk(actor);

	/*
	 * Face the tag
	 */
	// See where node is and where actor is
	GetPolyNode(hp, &nodex, &nodey);
	GetActorPos(actor, &nowx, &nowy);

	if (nowx == nodex && nowy == nodey) {
		// Stood at the tag, don't face in silly direction
		StandTag(actor, hp);
	} else {
		// Look towards polygon
		GetPolyMidBottom(hp, &nodex, &nodey);
		SetMoverDirection(pMover, GetDirection(nowx, nowy,
						nodex, nodey,
						GetMoverDirection(pMover),
						NOPOLY, YB_X1_5));
		SetMoverStanding(pMover);
	}
}

/**
 * FadeIn
 */
static void FadeIn() {
	FadeInMedium(NULL);
}

/**
 * FadeOut
 */
static void FadeOut() {
	FadeOutMedium(NULL);
}

/**
 * FadeMidi(in/out)
 */
static void FadeMidi(CORO_PARAM, int inout) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);
	assert(inout == FM_IN || inout == FM_OUT);

	// To prevent compiler complaining
	if (inout == FM_IN || inout == FM_OUT)
		CORO_SLEEP(1);
	CORO_END_CODE;
}

/**
 * Freeze the cursor, or not.
 */
static void FreezeCursor(bool bFreeze) {
	DoFreezeCursor(bFreeze);
}

/**
 * Guess what.
 */
static int GetInvLimit(int invno) {
	return InvGetLimit(invno);
}

/**
 * Ghost
 */
static void Ghost(int actor, int tColour, int tPalOffset) {
	SetSysVar(ISV_GHOST_ACTOR, actor);
	SetSysVar(ISV_GHOST_COLOUR,  tColour);
	SetSysVar(ISV_GHOST_BASE, tPalOffset);
	CreateGhostPalette(BgPal());
}

/**
 *
 */
static void HailScene(SCNHANDLE scene) {
	DoHailScene(scene);
}

/**
 * Returns TRUE if the game has been restarted, FALSE if not.
 */
static bool HasRestarted() {
	return bHasRestarted;
}

/**
 * See if an object is in the inventory.
 */
int Have(int object) {
	return (InventoryPos(object) != NOOBJECT);
}

/**
 * Returns which object is currently held.
 */
static int HeldObject() {
	return WhichItemHeld();
}

/**
 * Hides the specified actor
 */
static void HideActorFn(CORO_PARAM, int ano) {
	HideActor(coroParam, ano);
}

/**
 * Turn a blocking polygon off.
 */
static void HideBlock(int block) {
	DisableBlock(block);
}

/**
 * Turn an effect polygon off.
 */
static void HideEffect(int effect) {
	DisableEffect(effect);
}

/**
 * Turn a path polygon off.
 */
static void HidePath(int path) {
	DisablePath(path);
}

/**
 * Turn a refer polygon off.
 */
static void HideRefer(int refer) {
	DisableRefer(refer);
}

/**
 * Turn a tag polygon off.
 */
static void HideTag(CORO_PARAM, int tag, HPOLYGON hp) {
	// Tag could be zero, meaning calling tag
	DisableTag(coroParam, tag ? tag : GetTagPolyId(hp));
}

/**
 * Hold the specified object.
 */
static void Hold(int object) {
	HoldItem(object, false);
}

/**
 * HookScene(scene, entrance, transition)
 */
void HookScene(SCNHANDLE scene, int entrance, int transition) {
	SetHookScene(scene, entrance, transition);
}

/**
 * IdleTime
 */
static int IdleTime() {
	// If control is off, system is not idle
	if (!ControlIsOn()) {
		// Player doesn't currently have control
		ResetIdleTime();

		return 0;
	} else {
		// Player has control - return time since last event
		int x = getUserEventTime() / ONE_SECOND;

		return x;
	}
}

/**
 * Set flag if InstantScroll(on), reset if InstantScroll(off)
 */
void InstantScroll(int onoff) {
	bInstantScroll = (onoff != 0);
}

/**
 * invdepict
 */
static void InvDepict(int object, SCNHANDLE hFilm) {
	SetObjectFilm(object, hFilm);
}

/**
 * See if an object is in the inventory.
 */
int InInventory(int object) {
	return (InventoryPos(object) != INV_NOICON);
}

/**
 * Open an inventory.
 */
static void Inventory(int invno, bool escOn, int myEscape) {
	// Don't do it if it's not wanted
	if (escOn && myEscape != GetEscEvents())
		return;

	assert((invno == INV_1 || invno == INV_2)); // Trying to open illegal inventory

	PopUpInventory(invno);
}

/**
 * Alter inventory object's icon.
 */
static void InvPlay(int object, SCNHANDLE hFilm) {
	SetObjectFilm(object, hFilm);
}

/**
 * See if an object is in the inventory.
 */
static int InWhichInv(int object) {
	if (WhichItemHeld() == object)
		return 0;

	if (IsInInventory(object, INV_1))
		return 1;

	if (IsInInventory(object, INV_2))
		return 2;

	return -1;
}

/**
 * Kill an actor.
 */
static void KillActor(int actor) {
	DisableActor(actor);
}

/**
 * Turn a blocking polygon off.
 */
static void KillBlock(int block) {
	DisableBlock(block);
}

/**
 * Turn an exit off.
 */
static void KillExit(int exit) {
	DisableExit(exit);
}

/**
 * Turn a tag off.
 */
static void KillTag(CORO_PARAM, int tagno) {
	DisableTag(coroParam, tagno);
}

/**
 * Kills the specified global process
 */
static void KillGlobalProcess(uint32 procID) {
	xKillGlobalProcess(procID);
}

/**
 * Kills the specified process
 */
static void KillProcess(uint32 procID) {
	KillSceneProcess(procID);
}

/**
 * Returns the left or top offset of the screen.
 */
static int LToffset(int lort) {
	int Loffset, Toffset;

	PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
	return (lort == SCREENXPOS) ? Loffset : Toffset;
}

/**
 * Set new cursor position.
 */
static void MoveCursor(int x, int y) {
	SetCursorXY(x, y);

	controlX = x;		// Save these values so that
	controlY = y;		// control(on) doesn't undo this
}

/**
 * MoveTag(tag, x, y)
 */
void MoveTag(int tag, int x, int y, HPOLYGON hp) {
	// Tag could be zero, meaning calling tag
	MovePolygon(TAG, tag ? tag : GetTagPolyId(hp), x, y);
}

/**
 * MoveTagTo(tag, x, y)
 */
void MoveTagTo(int tag, int x, int y, HPOLYGON hp) {
	// Tag could be zero, meaning calling tag
	MovePolygonTo(TAG, tag ? tag : GetTagPolyId(hp), x, y);
}

/**
 * Triggers change to a new scene.
 */
void NewScene(CORO_PARAM, SCNHANDLE scene, int entrance, int transition) {
	// COROUTINE
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	if (TinselV2) {
		if (_vm->_bmv->MoviePlaying()) {
			_vm->_bmv->AbortMovie();
			CORO_SLEEP(2);
		}
	}

	SetNewScene(scene, entrance, transition);

	// Prevent tags and cursor re-appearing
	if (TinselV2)
		ControlStartOff();
	else
		GetControl(CONTROL_STARTOFF);

	if (TinselV1)
		++sceneCtr;

	// Prevent code subsequent to this call running before scene changes
	if (g_scheduler->getCurrentPID() != PID_MASTER_SCR)
		CORO_KILL_SELF();
	CORO_END_CODE;
}

/**
 * Disable dynamic blocking for current scene.
 */
static void NoBlocking() {
	SetNoBlocking(true);
}

/**
 * Define a no-scroll boundary for the current scene.
 */
static void NoScroll(int x1, int y1, int x2, int y2) {
	SetNoScroll(x1, y1, x2, y2);
}

/**
 * Hold the specified object.
 */
static void ObjectHeld(int object) {
	HoldItem(object);
}

/**
 * Set the top left offset of the screen.
 */
void Offset(EXTREME extreme, int x, int y) {
	KillScroll();

	if (TinselV2)
		DecodeExtreme(extreme, &x, &y);

	PlayfieldSetPos(FIELD_WORLD, x, y);
}

/**
 * OtherObject()
 */
int OtherObject(INV_OBJECT *pinvo) {
	assert(pinvo != NULL);

	// return held object or object clicked on - whichever is not the calling object

	// pinvo->id is the calling object
	// WhichItemHeld() gives the held object
	// GetIcon() gives the object clicked on

	assert(GetIcon() == pinvo->id || WhichItemHeld() == pinvo->id);

	if (GetIcon() == pinvo->id)
		return WhichItemHeld();
	else
		return GetIcon();
}

/**
 * Play a film.
 */
static void Play(CORO_PARAM, SCNHANDLE hFilm, int x, int y, int compit, int actorid, bool splay, int sfact,
		  bool escOn, int myEscape, bool bTop) {
	assert(hFilm != 0); // play(): Trying to play NULL film

	// COROUTINE
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);


	// Don't do CDPlay() for now if already escaped
	if (bEscapedCdPlay) {
		bEscapedCdPlay = false;
		return;
	}

	// Don't do it if it's not wanted
	if (escOn && myEscape != GetEscEvents())
		return;

	// If this actor is dead, call a stop to the calling process
	if (actorid && !actorAlive(actorid))
		CORO_KILL_SELF();

	// 7/4/95
	if (!escOn)
		myEscape = GetEscEvents();

	if (compit == 1) {
		// Play to completion before returning
		CORO_INVOKE_ARGS(PlayFilmc, (CORO_SUBCTX, hFilm, x, y, actorid, splay, sfact, escOn, myEscape, bTop));
	} else if (compit == 2) {
		error("play(): compit == 2 - please advise John");
	} else {
		// Kick off the play and return.
		CORO_INVOKE_ARGS(PlayFilm, (CORO_SUBCTX, hFilm, x, y, actorid, splay, sfact, escOn, myEscape, bTop));
	}
	CORO_END_CODE;
}

/**
 * Play a film
 */
static void Play(CORO_PARAM, SCNHANDLE hFilm, int x, int y, bool bComplete, int myEscape,
		bool bTop, TINSEL_EVENT event, HPOLYGON hPoly, int taggedActor) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	assert(hFilm != 0);

	// Don't do CdPlay() for now if already escaped
	if (bEscapedCdPlay) {
		bEscapedCdPlay = false;
		return;
	}

	if (event == TALKING) {
		int	actor;
		if (hPoly == NOPOLY) {
			// Must be a tagged actor

			assert(taggedActor && IsTaggedActor(taggedActor));
			actor = taggedActor;
		} else if (taggedActor == 0) {
			// Must be a polygon with an actor ID
			actor = GetTagPolyId(hPoly);
			assert(actor & ACTORTAG_KEY);
			actor &= ~ACTORTAG_KEY;
		}
		else {
			return;
		}

		SetActorTalking(actor, true);
		SetActorTalkFilm(actor, hFilm);
	}

	if (bComplete) {
		// Play to completion before returning
		CORO_INVOKE_ARGS(PlayFilmc, (CORO_SUBCTX, hFilm, x, y, 0, false, false, myEscape != 0, myEscape, bTop));
	} else {
		// Kick off the play and return.
		CORO_INVOKE_ARGS(PlayFilm, (CORO_SUBCTX, hFilm, x, y, myEscape, bTop));
	}

	CORO_END_CODE;
}


/**
 * Play a midi file.
 */
static void PlayMidi(CORO_PARAM, SCNHANDLE hMidi, int loop, bool complete) {
	// FIXME: This is a workaround for the FIXME below
	if (GetMidiVolume() == 0 || TinselV1PSX)
		return;

	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);
	assert(loop == MIDI_DEF || loop == MIDI_LOOP);

	PlayMidiSequence(hMidi, loop == MIDI_LOOP);

	// FIXME: The following check messes up the script arguments when
	// entering the secret door in the bookshelf in the library,
	// leading to a crash, when the music volume is set to 0 (MidiPlaying()
	// always false then).
	//
	// Why exactly this happens is unclear. An analysis of the involved
	// script(s) might reveal more.
	//
	// Note: This check&sleep was added in DW v2. It was most likely added
	// to ensure that the MIDI song started playing before the next opcode
	// is executed.
	if (!MidiPlaying())
		CORO_SLEEP(1);

	if (complete) {
		while (MidiPlaying())
			CORO_SLEEP(1);
	}
	CORO_END_CODE;
}

/**
 * Plays a movie
 */

static void PlayMovie(CORO_PARAM, SCNHANDLE hFileStem, int myEscape) {
	CORO_BEGIN_CONTEXT;
		int i;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	if (myEscape && myEscape != GetEscEvents())
		return;

	// Get rid of the cursor
	for (_ctx->i = 0; _ctx->i < 3; _ctx->i++) {
		DwHideCursor();
		DropCursor();
		CORO_SLEEP(1);
	}

	// They claim to be getting "Can't play two movies at once!" error
	while (_vm->_bmv->MoviePlaying())
		CORO_SLEEP(1);

	// Play the movie
	CORO_INVOKE_2(_vm->_bmv->PlayBMV, hFileStem, myEscape);

	CORO_END_CODE;
}

/**
 * Play some music
 */
static void PlayMusic(int tune) {
	_vm->_pcmMusic->startPlay(tune);
}

/**
 * Play a sample.
 * Tinsel 1 version
 */
static void PlaySample(CORO_PARAM, int sample, bool bComplete, bool escOn, int myEscape) {
	CORO_BEGIN_CONTEXT;
		Audio::SoundHandle handle;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);
	// Don't play SFX if voice is already playing
	if (_vm->_mixer->hasActiveChannelOfType(Audio::Mixer::kSpeechSoundType))
		return;

	// Don't do it if it's not wanted
	if (escOn && myEscape != GetEscEvents()) {
		_vm->_sound->stopAllSamples();		// Stop any currently playing sample
		return;
	}

	if (_vm->_config->_soundVolume != 0 && _vm->_sound->sampleExists(sample)) {
		_vm->_sound->playSample(sample, Audio::Mixer::kSFXSoundType, &_ctx->handle);

		if (bComplete) {
			while (_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
				// Abort if escapable and ESCAPE is pressed
				if (escOn && myEscape != GetEscEvents()) {
					_vm->_mixer->stopHandle(_ctx->handle);
					break;
				}

				CORO_SLEEP(1);
			}
		}
	} else {
		// Prevent Glitter lock-up
		CORO_SLEEP(1);
	}
	CORO_END_CODE;
}

/**
 * Play a sample
 * Tinsel 2 version
 */
static void PlaySample(CORO_PARAM, int sample, int x, int y, int flags, int myEscape) {
	int	priority;
	CORO_BEGIN_CONTEXT;
		Audio::SoundHandle handle;
		int myEscape;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	_ctx->myEscape = myEscape;

	// Not escapable if PlaySample(..., s)
	if (flags & PS_SUSTAIN) {
		_ctx->myEscape = 0;
		priority = PRIORITY_SPLAY2;
	} else {
		priority = PRIORITY_SPLAY1;
	}

	// Don't do anything if it's already been escaped
	if (_ctx->myEscape && _ctx->myEscape != GetEscEvents())
		return;

	if (_vm->_config->_soundVolume != 0 && _vm->_sound->sampleExists(sample)) {
		if (x == 0)
			x = -1;

		_vm->_sound->playSample(sample, 0, false, x, y, priority, Audio::Mixer::kSFXSoundType,
			&_ctx->handle);

		if (flags & PS_COMPLETE) {
			while (_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
				// Abort if escapable and ESCAPE is pressed
				if (_ctx->myEscape && _ctx->myEscape != GetEscEvents()) {
					_vm->_mixer->stopHandle(_ctx->handle);
					break;
				}

				CORO_SLEEP(1);
			}
		}
	} else {
		// Prevent Glitter lock-up
		CORO_SLEEP(1);
	}

	CORO_END_CODE;
}

/**
 * Move the cursor to the tagged actor's tag point.
 */
void PointActor(int actor) {
	int	x, y;

	// Only do this if the function is enabled
	if (!SysVar(SV_ENABLEPOINTTAG))
		return;

	assert(IsTaggedActor(actor));

	GetActorTagPos(actor, &x, &y, true);

	_vm->setMousePosition(Common::Point(x, y));
}

/**
 * Move the cursor to the tag's tag point.
 */
static void PointTag(int tagno, HPOLYGON hp) {
	int	x, y;
	SCNHANDLE junk;

	// Only do this if the function is enabled
	if (!SysVar(SV_ENABLEPOINTTAG))
		return;

	// Tag could be zero, meaning calling tag
	if (tagno == 0)
		tagno = GetTagPolyId(hp);

	GetTagTag(GetTagHandle(tagno), &junk, &x, &y);

	_vm->setMousePosition(Common::Point(x, y));
}

/**
 * PostActor("actor", event)
 */
static void PostActor(CORO_PARAM, int actor, TINSEL_EVENT event, HPOLYGON hp,
			   int taggedActor, int myEscape) {
	if (actor == -1) {
		actor = taggedActor;
		assert(hp == NOPOLY && taggedActor);
		assert(IsTaggedActor(actor));
	}

	if (IsTaggedActor(actor)) {
		assert(actor);
		ActorEvent(coroParam, actor, event, false, myEscape);
	} else {
		PostTag(coroParam, actor | ACTORTAG_KEY, event, hp, myEscape);
	}
}

/**
 * PostGlobalProcess(process#, event)
 */
static void PostGlobalProcess(CORO_PARAM, int procId, TINSEL_EVENT event, int myEscape) {
	GlobalProcessEvent(coroParam, procId, event, false, myEscape);
}

/**
 * PostObject(object, event)
 */
static void PostObject(CORO_PARAM, int object, TINSEL_EVENT event, int myEscape) {
	ObjectEvent(coroParam, object, event, false, myEscape);
}

/**
 * PostProcess(process#, event)
 */
static void PostProcess(CORO_PARAM, int procId, TINSEL_EVENT event, int myEscape) {
	SceneProcessEvent(coroParam, procId, event, false, myEscape);
}

/**
 * Posts an event to a specified tag
 */
static void PostTag(CORO_PARAM, int tagno, TINSEL_EVENT event, HPOLYGON hp, int myEscape) {
	// Tag could be zero, meaning calling tag
	if (tagno == 0) {
		assert(hp != NOPOLY);
		PolygonEvent(coroParam, hp, event, 0, false, myEscape);
	} else {
		assert(IsTagPolygon(tagno));
		PolygonEvent(coroParam, GetTagHandle(tagno), event, 0, false, myEscape);
	}
}

/**
 * Trigger pre-loading of a scene's data.
 */
static void PrepareScene(SCNHANDLE scene) {
#ifdef BODGE
	if (!ValidHandle(scene))
		return;
#endif
}

/**
 * Print the given text at the given place for the given time.
 */
static void Print(CORO_PARAM, int x, int y, SCNHANDLE text, int time, bool bSustain, bool escOn, int myEscape) {
	if (TinselV2)
		escOn = myEscape != 0;

	CORO_BEGIN_CONTEXT;
		OBJECT *pText;			// text object pointer
		int	myleftEvent;
		bool bSample;			// Set if a sample is playing
		Audio::SoundHandle handle;
		int timeout;
		int time;
	CORO_END_CONTEXT(_ctx);

	bool	bJapDoPrintText;	// Bodge to get-around Japanese bodge

	CORO_BEGIN_CODE(_ctx);

	_ctx->pText = NULL;
	_ctx->bSample = false;

	// Don't do it if it's not wanted
	if (escOn && myEscape != GetEscEvents())
		return;

	if (!TinselV2) {
		// Kick off the voice sample
		if (_vm->_config->_voiceVolume != 0 && _vm->_sound->sampleExists(text)) {
			_vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
			_ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle);
		}
	}

	// Get the string
	LoadStringRes(text, TextBufferAddr(), TBUFSZ);

	// Calculate display time
	bJapDoPrintText = false;
	if (time == 0) {
		// This is a 'talky' print
		_ctx->time = TextTime(TextBufferAddr());

		// Cut short-able if sustain was not set
		_ctx->myleftEvent = bSustain ? 0 : GetLeftEvents();
	} else {
		_ctx->time = time * ONE_SECOND;
		_ctx->myleftEvent = (TinselV2 && !bSustain) ? GetLeftEvents() : 0;
		if (isJapanMode())
			bJapDoPrintText = true;
	}

	// Print the text
	if (TinselV2) {
		int Loffset, Toffset;
		PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
		_ctx->pText = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS),
			TextBufferAddr(), 0, x - Loffset, y - Toffset, GetTagFontHandle(),
			TXT_CENTRE, 0);
		assert(_ctx->pText);

		// Adjust x, y, or z if necessary
		KeepOnScreen(_ctx->pText, &x, &y);
		if (IsTopWindow())
			MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT);

	} else if (bJapDoPrintText || (!isJapanMode() && (_vm->_config->_useSubtitles || !_ctx->bSample))) {
		int Loffset, Toffset;	// Screen position
		PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
		_ctx->pText = ObjectTextOut(coroParam, GetPlayfieldList(FIELD_STATUS), TextBufferAddr(),
					0, x - Loffset, y - Toffset,
					TinselV2 ? GetTagFontHandle() : GetTalkFontHandle(), TXT_CENTRE);
		assert(_ctx->pText); // string produced NULL text
		if (IsTopWindow())
			MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT);

		/*
		 * New feature: Don't go off the side of the background
		 */
		int	shift;
		shift = MultiRightmost(_ctx->pText) + 2;
		if (shift >= BgWidth())			// Not off right
			MultiMoveRelXY(_ctx->pText, BgWidth() - shift, 0);
		shift = MultiLeftmost(_ctx->pText) - 1;
		if (shift <= 0)					// Not off left
			MultiMoveRelXY(_ctx->pText, -shift, 0);
		shift = MultiLowest(_ctx->pText);
		if (shift > BgHeight())			// Not off bottom
			MultiMoveRelXY(_ctx->pText, 0, BgHeight() - shift);
	}

	// Give up if nothing printed and no sample
	if (_ctx->pText == NULL && !_ctx->bSample)
		return;

	// Leave it up until time runs out or whatever
	if (TinselV2) {
		do {
			CORO_SLEEP(1);

			// Cancelled?
			if ( (myEscape && myEscape != GetEscEvents())
					|| (!bSustain && LeftEventChange(_ctx->myleftEvent)))
				break;

		} while (_ctx->time-- >= 0);

	} else {
		_ctx->timeout = SAMPLETIMEOUT;
		do {
			CORO_SLEEP(1);

			// Abort if escapable and ESCAPE is pressed
			// Abort if left click - hardwired feature for talky-print!
			// Will be ignored if myleftevent happens to be 0!
			// Abort if sample times out
			if ((escOn && myEscape != GetEscEvents())
			|| (_ctx->myleftEvent && _ctx->myleftEvent != GetLeftEvents())
			|| (_ctx->bSample && --_ctx->timeout <= 0))
				break;

			if (_ctx->bSample) {
				// Wait for sample to end whether or not
				if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
					if (_ctx->pText == NULL || _vm->_config->_textSpeed == DEFTEXTSPEED)				{
						// No text or speed modification - just depends on sample
						break;
					} else {
						// Must wait for time
						_ctx->bSample = false;
					}
				}
			} else {
				// No sample - just depends on time
				if (_ctx->time-- <= 0)
					break;
			}

		} while (1);
	}

	// Delete the text
	if (_ctx->pText != NULL)
		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
	_vm->_mixer->stopHandle(_ctx->handle);

	CORO_END_CODE;
}


static void PrintObjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, OBJECT *&pText, const int textx, const int texty, const int item);
static void PrintObjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText);

/**
 * Print the given inventory object's name or whatever.
 */
static void PrintObj(CORO_PARAM, const SCNHANDLE hText, const INV_OBJECT *pinvo, const int event, int myEscape) {
	CORO_BEGIN_CONTEXT;
		OBJECT *pText;		// text object pointer
		int	textx, texty;
		int	item;
		bool bSample;
		int sub;
		Audio::SoundHandle handle;
		int ticks;
		int timeout;
		bool bTookControl;
		int myEscape;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	assert(pinvo != 0); // PrintObj() may only be called from an object code block
	_ctx->myEscape = myEscape;

	if (hText == (SCNHANDLE)-1) {	// 'OFF'
		bNotPointedRunning = true;
		return;
	}
	if (hText == (SCNHANDLE)-2) {	// 'ON'
		bNotPointedRunning = false;
		return;
	}

	// Don't do it if it's not wanted
	if (TinselV2 && (myEscape) && (myEscape != GetEscEvents()))
		return;

	/*
	* Find out which icon the cursor is over, and where to put the text.
	*/
	GetCursorXY(&_ctx->textx, &_ctx->texty, false);	// Cursor position..
	_ctx->item = InvItem(&_ctx->textx, &_ctx->texty, true);	// ..to text position
	if (_ctx->item == INV_NOICON)
		return;

	/*
	* POINT/other event PrintObj() arbitration...
	*/
	if (event != POINTED) {
		bNotPointedRunning = true;	// Get POINTED text to die
		CORO_SLEEP(1);		// Give it chance to
	} else if (!TinselV2)
		bNotPointedRunning = false;	// There may have been an OFF without an ON

	// Make multi-ones escape
	if (TinselV2 && (SubStringCount(hText) > 1) && !_ctx->myEscape)
		_ctx->myEscape = GetEscEvents();

	// Loop once for Tinsel 1 strings, and for Tinsel 2 however many lines are needed
	for (_ctx->sub = 0; _ctx->sub < (TinselV2 ? SubStringCount(hText) : 1); _ctx->sub++) {
		if (_ctx->myEscape && _ctx->myEscape != GetEscEvents())
			break;

		if (!_vm->_sound->sampleExists(hText))
			_ctx->bSample = false;
		else {
			// Kick off the voice sample
			_vm->_sound->playSample(hText, _ctx->sub, false, -1, -1, PRIORITY_TALK,
				Audio::Mixer::kSpeechSoundType, &_ctx->handle);
			_ctx->bSample = true;
		}

		// Display the text and set it's Z position
		if (event == POINTED || (!isJapanMode() && (_vm->_config->_useSubtitles || !_ctx->bSample))) {
			int	xshift;

			// Get the text string
			if (TinselV2)
				LoadSubString(hText, _ctx->sub, TextBufferAddr(), TBUFSZ);
			else
				LoadStringRes(hText, TextBufferAddr(), TBUFSZ);

			_ctx->pText = ObjectTextOut(coroParam, GetPlayfieldList(FIELD_STATUS), TextBufferAddr(),
						0, _ctx->textx, _ctx->texty, GetTagFontHandle(), TXT_CENTRE);
			assert(_ctx->pText); // PrintObj() string produced NULL text

			MultiSetZPosition(_ctx->pText, Z_INV_ITEXT);

			if (TinselV2)
				KeepOnScreen(_ctx->pText, &_ctx->textx, &_ctx->texty);
			else {
				// Don't go off the side of the screen
				xshift = MultiLeftmost(_ctx->pText);
				if (xshift < 0) {
					MultiMoveRelXY(_ctx->pText, - xshift, 0);
					_ctx->textx -= xshift;
				}
				xshift = MultiRightmost(_ctx->pText);
				if (xshift > SCREEN_WIDTH) {
					MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0);
					_ctx->textx += SCREEN_WIDTH - xshift;
				}
			}
		} else
			_ctx->pText = NULL;

		if (TinselV2) {
			if (event == POINTED) {
				/*
				* PrintObj() called from POINT event
				*/
				// Have to give way to non-POINTED-generated text
				// and go away if the item gets picked up
				int x, y;
				do {
					// Give up if this item gets picked up
					if (WhichItemHeld() == pinvo->id)
						break;

					// Give way to non-POINTED-generated text
					if (bNotPointedRunning) {
						// Delete the text, and wait for the all-clear
						MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
						_ctx->pText = NULL;

						while (bNotPointedRunning)
							CORO_SLEEP(1);

						GetCursorXY(&x, &y, false);
						if (InvItem(&x, &y, false) != _ctx->item)
							break;

						// Re-display in the same place
						LoadStringRes(hText, TextBufferAddr(), TBUFSZ);
						_ctx->pText = ObjectTextOut(nullContext, GetPlayfieldList(FIELD_STATUS),
							TextBufferAddr(), 0, _ctx->textx, _ctx->texty, GetTagFontHandle(),
							TXT_CENTRE, 0);
						assert(_ctx->pText);

						KeepOnScreen(_ctx->pText, &_ctx->textx, &_ctx->texty);
						MultiSetZPosition(_ctx->pText, Z_INV_ITEXT);
					}

					CORO_SLEEP(1);

					// Carry on until the cursor leaves this icon
					GetCursorXY(&x, &y, false);

				} while (InvItemId(x, y) == pinvo->id);
			} else {
				/*
				 * PrintObj() called from other event
				 */
				_ctx->myEscape = GetLeftEvents();
				_ctx->bTookControl = GetControl();

				// Display for a time, but abort if conversation gets hidden
				if (_ctx->pText)
					_ctx->ticks = TextTime(TextBufferAddr());
				_ctx->timeout = SAMPLETIMEOUT;

				for (;;) {
					CORO_SLEEP(1);

					// Abort if left click - hardwired feature for talky-print!
					// Abort if sample times out
					// Abort if conversation hidden
					if (LeftEventChange(_ctx->myEscape)
							|| --_ctx->timeout <= 0
							|| ConvIsHidden())
						break;

					if (_ctx->bSample) {
						// Wait for sample to end whether or not
						if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
							if (_ctx->pText == NULL || _vm->_config->_textSpeed == DEFTEXTSPEED) {
								// No text or speed modification - just depends on sample
								break;
							} else {
								// Must wait for time
								_ctx->bSample = false;
							}
						}

						// Decrement the subtitles timeout counter
						if (_ctx->ticks > 0) --_ctx->ticks;

					} else {
						// No sample - just depends on time
						if (_ctx->ticks-- <= 0)
							break;
					}
				}

				if (_ctx->bTookControl)
					ControlOn();		// Free control if we took it
			}

		} else {
			if (event == POINTED) {
				// FIXME: Is there ever an associated sound if in POINTED mode???
				assert(!_vm->_sound->sampleExists(hText));
				CORO_INVOKE_ARGS(PrintObjPointed, (CORO_SUBCTX, hText, pinvo, _ctx->pText, _ctx->textx, _ctx->texty, _ctx->item));
			} else {
				CORO_INVOKE_2(PrintObjNonPointed, hText, _ctx->pText);
			}
		}

		// Delete the text, if haven't already
		if (_ctx->pText)
			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);

		// If it hasn't already finished, stop sample
		if (_ctx->bSample)
			_vm->_mixer->stopHandle(_ctx->handle);
	}

	// Let POINTED text back in if this is the last
	if (event != POINTED)
		bNotPointedRunning = false;

	CORO_END_CODE;
}

static void PrintObjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, OBJECT *&pText, const int textx, const int texty, const int item) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);
		// Have to give way to non-POINTED-generated text
		// and go away if the item gets picked up
		int	x, y;
		do {
			// Give up if this item gets picked up
			if (WhichItemHeld() == pinvo->id)
				break;

			// Give way to non-POINTED-generated text
			if (bNotPointedRunning) {
				// Delete the text, and wait for the all-clear
				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), pText);
				pText = NULL;
				while (bNotPointedRunning)
					CORO_SLEEP(1);

				GetCursorXY(&x, &y, false);
				if (InvItem(&x, &y, false) != item)
					break;

				// Re-display in the same place
				LoadStringRes(text, TextBufferAddr(), TBUFSZ);
				pText = ObjectTextOut(coroParam, GetPlayfieldList(FIELD_STATUS), TextBufferAddr(),
							0, textx, texty, GetTagFontHandle(), TXT_CENTRE);
				assert(pText); // PrintObj() string produced NULL text
				MultiSetZPosition(pText, Z_INV_ITEXT);
			}

			CORO_SLEEP(1);

			// Carry on until the cursor leaves this icon
			GetCursorXY(&x, &y, false);
		} while (InvItemId(x, y) == pinvo->id);

	CORO_END_CODE;
}

static void PrintObjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText) {
	CORO_BEGIN_CONTEXT;
		bool bSample;		// Set if a sample is playing
		Audio::SoundHandle handle;

		int myleftEvent;
		bool took_control;
		int	ticks;
		int	timeout;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);
		// Kick off the voice sample
		if (_vm->_config->_voiceVolume != 0 && _vm->_sound->sampleExists(text)) {
			_vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
			_ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle);
		} else
			_ctx->bSample = false;

		_ctx->myleftEvent = GetLeftEvents();
		_ctx->took_control = GetControl(CONTROL_OFF);

		// Display for a time, but abort if conversation gets hidden
		if (isJapanMode())
			_ctx->ticks = JAP_TEXT_TIME;
		else if (pText)
			_ctx->ticks = TextTime(TextBufferAddr());
		else
			_ctx->ticks = 0;

		_ctx->timeout = SAMPLETIMEOUT;
		do {
			CORO_SLEEP(1);
			--_ctx->timeout;

			// Abort if left click - hardwired feature for talky-print!
			// Abort if sample times out
			// Abort if conversation hidden
			if (_ctx->myleftEvent != GetLeftEvents() || _ctx->timeout <= 0 || ConvIsHidden())
				break;

			if (_ctx->bSample) {
				// Wait for sample to end whether or not
				if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
					if (pText == NULL || _vm->_config->_textSpeed == DEFTEXTSPEED) {
						// No text or speed modification - just depends on sample
						break;
					} else {
						// Must wait for time
						_ctx->bSample = false;
					}
				}

				// Decrement the subtitles timeout counter
				if (_ctx->ticks > 0) --_ctx->ticks;

			} else {
				// No sample - just depends on time
				if (_ctx->ticks-- <= 0)
					break;
			}
		} while (1);

		bNotPointedRunning = false;	// Let POINTED text back in

		if (_ctx->took_control)
			Control(CONTROL_ON);	// Free control if we took it

		_vm->_mixer->stopHandle(_ctx->handle);

	CORO_END_CODE;
}

/**
 * Register the fact that this poly would like its tag displayed.
 */
static void PrintTag(HPOLYGON hp, SCNHANDLE text, int actor = 0, bool bCursor = false) {
	// printtag() may only be called from a polygon code block in Tinsel 1, or
	// additionally from a moving actor code block in Tinsel 2
	assert((hp != NOPOLY) || (TinselV2 && (actor != 0)));

	if (hp != NOPOLY) {
		// Poly handling
		if (TinselV2)
			SetPolyTagWanted(hp, true, bCursor, text);
		else if (PolyTagState(hp) == TAG_OFF) {
			SetPolyTagState(hp, TAG_ON);
			SetPolyTagHandle(hp, text);
		}
	} else {
		// Moving actor handling
		SetActorTagWanted(actor, true, bCursor, text);
	}
}

/**
 * Quits the game
 */
static void QuitGame() {
	StopMidi();
	StopSample();
	_vm->quitGame();
}

/**
 * Return a random number between optional limits.
 */
static int RandomFn(int n1, int n2, int norpt) {
	int i = 0;
	uint32 value;

	// In DW1 demo, upper/lower limit can be reversed
	if (n2 < n1) SWAP(n1, n2);

	do {
		value = n1 + _vm->getRandomNumber(n2 - n1);
	} while ((lastValue == value) && (norpt == RAND_NORPT) && (++i <= 10));

	lastValue = value;
	return value;
}

/**
 * ResetIdleTime
 */
void ResetIdleTime() {
	resetUserEventTime();
}

/**
 * FnRestartGame
 */
void FnRestartGame() {
	// TODO: Tinsel 2 comments out the 2 calls, but I'm not sure that this should be done
	StopMidi();
	StopSample();

	bRestart = true;
	sceneCtr = 0;
}

/**
 * Restore saved scene.
 */
static void RestoreScene(CORO_PARAM, TRANSITS transition) {
	// COROUTINE
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	if (TinselV2) {
		if (_vm->_bmv->MoviePlaying()) {
			_vm->_bmv->AbortMovie();
			CORO_SLEEP(2);
		}

		CuttingScene(false);

	} else {
		UnSuspendHook();
	}

	TinselRestoreScene(transition == TRANS_FADE);

	CORO_END_CODE;
}

/**
 * Resumes the last game
 */
void ResumeLastGame() {
	RestoreGame(NewestSavedGame());
}

/**
 * Returns the current run mode
 */
static int RunMode() {
	return clRunMode;
}

/**
 * SamplePlaying
 */
static bool SamplePlaying(bool escOn, int myEscape) {
	// escape effects introduced 14/12/95 to fix
	//	 while (sampleplaying()) pause;

	if (escOn && myEscape != GetEscEvents())
		return false;

	return _vm->_sound->sampleIsPlaying();
}

/**
 * Save current scene.
 */
void SaveScene(CORO_PARAM) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	if (TinselV2) {
		CuttingScene(true);
		SendSceneTinselProcess(LEAVE_T2);
		CORO_GIVE_WAY;

		CORO_INVOKE_0(TinselSaveScene);
	} else {
		CORO_INVOKE_0(TinselSaveScene);
		SuspendHook();
	}

	CORO_END_CODE;
}

/**
 * ScalingReels
 */
static void ScalingReels(int actor, int scale, int direction,
		SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away) {

	SetScalingReels(actor, scale, direction, left, right, forward, away);
}

/**
 * Return the icon that caused the CONVERSE event.
 */
static int ScanIcon() {
	return GetIcon();
}

/**
 * Scroll the screen to target co-ordinates.
 */
static void Scroll(CORO_PARAM, EXTREME extreme, int xp, int yp, int xIter, int yIter, bool bComp, bool escOn, int myEscape) {
	CORO_BEGIN_CONTEXT;
		int	thisScroll;
		int x, y;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	++scrollNumber;
	_ctx->x = xp;
	_ctx->y = yp;

	if ((TinselV2 && bInstantScroll) || (escOn && myEscape != GetEscEvents())) {
		// Instant completion!
		Offset(extreme, _ctx->x, _ctx->y);
	} else {
		_ctx->thisScroll = scrollNumber;
		if (TinselV2)
			DecodeExtreme(extreme, &_ctx->x, &_ctx->y);

		ScrollTo(_ctx->x, _ctx->y, xIter, yIter);

		if (bComp) {
			int	Loffset, Toffset;
			do {
				CORO_SLEEP(1);

				// If escapable and ESCAPE is pressed...
				if (escOn && myEscape != GetEscEvents()) {
					// Instant completion!
					Offset(extreme, _ctx->x, _ctx->y);
					break;
				}

				// give up if have been superseded
				if (_ctx->thisScroll != scrollNumber)
					CORO_KILL_SELF();

				PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
			} while (Loffset != _ctx->x || Toffset != _ctx->y);
		} else if (TinselV2 && myEscape) {
			static SCROLL_MONITOR sm;

			// Scroll is escapable even though we're not waiting for it
			sm.x = _ctx->x;
			sm.y = _ctx->y;
			sm.thisScroll = scrollNumber;
			sm.myEscape = myEscape;
			g_scheduler->createProcess(PID_TCODE, ScrollMonitorProcess, &sm, sizeof(sm));
		}
	}
	CORO_END_CODE;
}

/**
 * ScrollParameters
 */
static void ScrollParameters(int xTrigger, int xDistance, int xSpeed, int yTriggerTop,
		int yTriggerBottom, int yDistance, int ySpeed) {
	SetScrollParameters(xTrigger, xDistance, xSpeed,
			yTriggerTop, yTriggerBottom, yDistance, ySpeed);
}

/**
 * SendActor("actor", event)
 */
int SendActor(CORO_PARAM, int actor, TINSEL_EVENT event, HPOLYGON hp, int myEscape) {
	bool result;

	if (IsTaggedActor(actor)) {
		assert(actor);
		ActorEvent(coroParam, actor, event, true, myEscape, &result);
	} else {
		SendTag(coroParam, actor | ACTORTAG_KEY, event, hp, myEscape, &result);
	}

	return result;
}

/**
 * SendGlobalProcess(process#, event)
 */
static int SendGlobalProcess(CORO_PARAM, int procId, TINSEL_EVENT event, int myEscape) {
	return GlobalProcessEvent(coroParam, procId, event, true, myEscape);
}

/**
 * SendObject(object, event)
 */
static int SendObject(CORO_PARAM, int object, TINSEL_EVENT event, int myEscape) {
	bool result;
	ObjectEvent(coroParam, object, event, true, myEscape, &result);
	return result;
}

/**
 * SendProcess(process#, event)
 */
static int SendProcess(CORO_PARAM, int procId, TINSEL_EVENT event, int myEscape) {
	bool result;
	SceneProcessEvent(coroParam, procId, event, true, myEscape, &result);
	return result;
}

/**
 * SendTag(tag#, event)
 */
static void SendTag(CORO_PARAM, int tagno, TINSEL_EVENT event, HPOLYGON hp, int myEscape, bool *result) {
	// Tag could be zero, meaning calling tag
	if (tagno == 0) {
		assert(hp != NOPOLY);

		PolygonEvent(coroParam, hp, event, 0, true, myEscape, result);
	} else {
		assert(IsTagPolygon(tagno));

		PolygonEvent(coroParam, GetTagHandle(tagno), event, 0, true, myEscape, result);
	}
}

/**
 * Un-kill an actor.
 */
static void SetActor(int actor) {
	EnableActor(actor);
}

/**
 * Turn a blocking polygon on.
 */

static void SetBlock(int blockno) {
	EnableBlock(blockno);
}

/**
 * Turn an exit on.
 */

static void SetExit(int exitno) {
	EnableExit(exitno);
}

/**
 * Guess what.
 */
static void SetInvLimit(int invno, int n) {
	InvSetLimit(invno, n);
}

/**
 * Guess what.
 */
static void SetInvSize(int invno, int MinWidth, int MinHeight,
		int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) {
	InvSetSize(invno, MinWidth, MinHeight, StartWidth, StartHeight, MaxWidth, MaxHeight);
}

/**
 * Guess what.
 */
static void SetLanguage(LANGUAGE lang) {
	assert(lang == TXT_ENGLISH || lang == TXT_FRENCH
	     || lang == TXT_GERMAN  || lang == TXT_ITALIAN
	     || lang == TXT_SPANISH); // ensure language is valid

	ChangeLanguage(lang);
}

/**
 * Set palette
 */
static void SetPalette(SCNHANDLE hPal, bool escOn, int myEscape) {
	// Don't do it if it's not wanted
	if (escOn && myEscape != GetEscEvents())
		return;

	ChangePalette(hPal);
}

/**
 * SetSystemString
 */

static void SetSystemString(int stringId, SCNHANDLE hString) {
	SetSysString(stringId, hString);
}

/**
 * Set a system variable
 */
static void SetSystemVar(int varId, int newValue) {
	SetSysVar(varId, newValue);
}

/**
 * Turn a tag on.
 */
static void SetTag(CORO_PARAM, int tagno) {
	EnableTag(coroParam, tagno);
}

/**
 * Initialise a timer.
 */
static void SetTimer(int timerno, int start, bool up, bool frame) {
	StartTimer(timerno, start, up != 0, frame != 0);
}

/**
 * Shell("cmdline")
 */
static void Shell(SCNHANDLE commandLine) {
	LoadStringRes(commandLine, TextBufferAddr(), TBUFSZ);
	error("Tried to execute shell command \"%s\"", TextBufferAddr());
}

/**
 * Don't hide an actors graphics.
 */
static void ShowActorFn(CORO_PARAM, int actor) {
	ShowActor(coroParam, actor);
}

/**
 * Turn a blocking polygon on.
 */
void ShowBlock(int blockno) {
	EnableBlock(blockno);
}

/**
 * Turn an effect polygon on.
 */
void ShowEffect(int effect) {
	EnableEffect(effect);
}

#ifdef DEBUG
/**
 * Enable display of diagnostic co-ordinates.
 */
static void showpos() {
	setshowpos();
}

/**
 * Enable display of diagnostic co-ordinates.
 */
static void showstring() {
	setshowstring();
}
#endif

/**
 * Shows the main menu
 */
static void ShowMenu() {
	OpenMenu(MAIN_MENU);
}

/**
 * Turn a path on.
 */
static void ShowPath(int path) {
	EnablePath(path);
}

/**
 * Turn a refer on.
 */
void ShowRefer(int refer) {
	EnableRefer(refer);
}

/**
 * Turn a tag on.
 */
static void ShowTag(CORO_PARAM, int tag, HPOLYGON hp) {
	// Tag could be zero, meaning calling tag
	EnableTag(coroParam, tag ? tag : GetTagPolyId(hp));
}

/**
 * Special play - slow down associated actor's movement while the play
 * is running. After the play, position the actor where the play left
 * it and continue walking, if the actor still is.
 */
static void SPlay(CORO_PARAM, int sf, SCNHANDLE film, int x, int y, bool complete, int actorid, bool escOn, int myEscape) {
	// Don't do it if it's not wanted
	if (escOn && myEscape != GetEscEvents())
		return;

	Play(coroParam, film, x, y, complete, actorid, true, sf, escOn, myEscape, false);
}

/**
 * (Re)Position an actor.
 * If moving actor is not around yet in this scene, start it up.
 */
void Stand(CORO_PARAM, int actor, int x, int y, SCNHANDLE hFilm) {
	CORO_BEGIN_CONTEXT;
		PMOVER pMover;		// Moving actor structure
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	_ctx->pMover = GetMover(actor);
	assert(!TinselV2 || (_ctx->pMover != NULL));

	if (_ctx->pMover) {
		if (TinselV2) {
			// New special. If no paths, just ignore this
			if (PathCount() == 0)
				return;

			// Another new special.
			// If lead actor, and TalkVia, ignore
			if ((actor == GetLeadId() || actor == LEAD_ACTOR) && SysVar(ISV_DIVERT_ACTOR))
				return;
		}

		if (!MoverIs(_ctx->pMover)) {
			// create a moving actor process
			MoverProcessCreate(x, y, (actor == LEAD_ACTOR) ? GetLeadId() : actor, _ctx->pMover);

			if (hFilm == TF_NONE) {
				// Make sure there is an assigned actorObj
				while (!_ctx->pMover->actorObj)
					CORO_SLEEP(1);

				SetMoverStanding(_ctx->pMover);
			} else {
				// Check hFilm against certain constants. Note that a switch statement isn't
				// used here because it would interfere with our co-routine implementation
				if (hFilm == TF_UP) {
					if (TinselV2) CORO_GIVE_WAY;
					SetMoverDirection(_ctx->pMover, AWAY);
					SetMoverStanding(_ctx->pMover);
				} else if (hFilm == TF_DOWN) {
					if (TinselV2) CORO_GIVE_WAY;
					SetMoverDirection(_ctx->pMover, FORWARD);
					SetMoverStanding(_ctx->pMover);
				} else if (hFilm == TF_LEFT) {
					if (TinselV2) CORO_GIVE_WAY;
					SetMoverDirection(_ctx->pMover, LEFTREEL);
					SetMoverStanding(_ctx->pMover);
				} else if (hFilm == TF_RIGHT) {
					if (TinselV2) CORO_GIVE_WAY;
					SetMoverDirection(_ctx->pMover, RIGHTREEL);
					SetMoverStanding(_ctx->pMover);
				} else if (hFilm != TF_NONE) {
					if (TinselV2) CORO_GIVE_WAY;
					AlterMover(_ctx->pMover, hFilm, AR_NORMAL);
				}
			}
		} else {
			switch (hFilm) {
			case TF_NONE:
				if (x != -1 && y != -1)
					PositionMover(_ctx->pMover, x, y);
				break;

			case TF_UP:
				SetMoverDirection(_ctx->pMover, AWAY);
				if (x != -1 && y != -1)
					PositionMover(_ctx->pMover, x, y);
				SetMoverStanding(_ctx->pMover);
				break;
			case TF_DOWN:
				SetMoverDirection(_ctx->pMover, FORWARD);
				if (x != -1 && y != -1)
					PositionMover(_ctx->pMover, x, y);
				SetMoverStanding(_ctx->pMover);
				break;
			case TF_LEFT:
				SetMoverDirection(_ctx->pMover, LEFTREEL);
				if (x != -1 && y != -1)
					PositionMover(_ctx->pMover, x, y);
				SetMoverStanding(_ctx->pMover);
				break;
			case TF_RIGHT:
				SetMoverDirection(_ctx->pMover, RIGHTREEL);
				if (x != -1 && y != -1)
					PositionMover(_ctx->pMover, x, y);
				SetMoverStanding(_ctx->pMover);
				break;

			default:
				if (x != -1 && y != -1)
					PositionMover(_ctx->pMover, x, y);
				AlterMover(_ctx->pMover, hFilm, AR_NORMAL);
				break;
			}
		}
	} else if (actor == NULL_ACTOR) {
		//
	} else {
		assert(hFilm != 0); // Trying to play NULL film

		// Kick off the play and return.
		CORO_INVOKE_ARGS(PlayFilm, (CORO_SUBCTX, hFilm, x, y, actor, false, 0, false, 0, false));
	}

	CORO_END_CODE;
}

/**
 * Position the actor at the polygon's tag node.
 */
static void StandTag(int actor, HPOLYGON hp) {
	SCNHANDLE hFilm;
	int	pnodex, pnodey;

	assert(hp != NOPOLY); // StandTag() may only be called from a polygon code block

	// Where to stand
	GetPolyNode(hp, &pnodex, &pnodey);

	// Lead actor uses tag node film
	hFilm = GetPolyFilm(hp);

	// other actors can use direction
	if (TinselV2) {
		if (actor != LEAD_ACTOR && actor != GetLeadId()
				&& hFilm != TF_UP && hFilm != TF_DOWN
				&& hFilm != TF_LEFT && hFilm != TF_RIGHT)
			hFilm = 0;

		Stand(nullContext, actor, pnodex, pnodey, hFilm);

	} else if (hFilm && (actor == LEAD_ACTOR || actor == GetLeadId()))
		Stand(nullContext, actor, pnodex, pnodey, hFilm);
	else
		Stand(nullContext, actor, pnodex, pnodey, 0);
}


/**
 * StartGlobalProcess
 */
static void StartGlobalProcess(CORO_PARAM, uint32 procID) {
	GlobalProcessEvent(coroParam, procID, STARTUP, false, 0);
}

/**
 * StartProcess
 */
static void StartProcess(CORO_PARAM, uint32 procID) {
	SceneProcessEvent(coroParam, procID, STARTUP, false, 0);
}

/**
 * Initialise a timer.
 */
static void StartTimerFn(int timerno, int start, bool up, int fs) {
	StartTimer(timerno, start, up, fs);
}

void StopMidiFn() {
	StopMidi();		// Stop any currently playing midi
}

/**
 * Kill a specific sample, or all samples.
 */
void StopSample(int sample) {
	if (sample == -1)
		_vm->_sound->stopAllSamples();		// Stop any currently playing sample
	else
		_vm->_sound->stopSpecSample(sample, 0);
}

/**
 * Kill a moving actor's walk.
 */
static void StopWalk(int actor) {
	PMOVER pMover;

	pMover = GetMover(actor);
	assert(pMover);

	if (TinselV2) {
		if (MoverHidden(pMover))
			return;

		StopMover(pMover);		// Cause the actor to stop
	} else {
		GetToken(pMover->actorToken);	// Kill the walk process
		pMover->bStop = true;			// Cause the actor to stop
		FreeToken(pMover->actorToken);
	}
}

/**
 * Subtitles on/off
 */
static void Subtitles(int onoff) {
	assert (onoff == ST_ON || onoff == ST_OFF);

	if (isJapanMode())
		return;	// Subtitles are always off in JAPAN version (?)

	_vm->_config->_useSubtitles = (onoff == ST_ON);
}

/**
 * Special walk.
 * Walk into or out of a legal path.
 */
static void Swalk(CORO_PARAM, int actor, int x1, int y1, int x2, int y2, SCNHANDLE film, int32 zOverride, bool escOn, int myEscape) {
	CORO_BEGIN_CONTEXT;
		bool	bTookControl;			// Set if this function takes control
	CORO_END_CONTEXT(_ctx);

	HPOLYGON hPath;

	CORO_BEGIN_CODE(_ctx);

	// Don't do it if it's not wanted
	if (escOn && myEscape != GetEscEvents()) {
		if (TinselV2) {
			if (x2 == -1 && y2 == -1)
				CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x1, y1, 0));
			else
				CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x2, y2, 0));
		}

		return;
	}

	// For lead actor, lock out the user (if not already locked out)
	if (actor == GetLeadId() || actor == LEAD_ACTOR) {
		_ctx->bTookControl = GetControl(CONTROL_OFFV2);
		if (TinselV2 && _ctx->bTookControl)
			RestoreMainCursor();
	} else {
		_ctx->bTookControl = false;
	}

	if (TinselV2 && (x2 == -1) && (y2 == -1)) {
		// First co-ordinates are the destination
		x2 = x1;
		y2 = y1;
	} else {
		// Stand at the start co-ordinates
		hPath = InPolygon(x1, y1, PATH);

		if (hPath != NOPOLY) {
			// Walking out of a path
			CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x1, y1, 0));
		} else {
			hPath = InPolygon(x2, y2, PATH);
			// One of them has to be in a path
			assert(hPath != NOPOLY); //one co-ordinate must be in a legal path

			// Walking into a path
			CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x2, y2, 0));	// Get path's characteristics
			CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x1, y1, 0));
		}

		if (TinselV2 && (zOverride != -1)) {
			PMOVER pMover = GetMover(actor);
			assert(pMover);

			SetMoverZ(pMover, y1, zOverride);
		}
	}

	CORO_INVOKE_ARGS(Walk, (CORO_SUBCTX, actor, x2, y2, film, 0, true, zOverride, escOn, myEscape));

	// Free control if we took it
	if (_ctx->bTookControl)
		Control(CONTROL_ON);

	CORO_END_CODE;
}

/**
 * Gets a system variable
 */
static int SystemVar(int varId) {
	return SysVar(varId);
}

/**
 * Define a tagged actor.
 */
static void TagActor(int actor, SCNHANDLE text, int tp) {
	Tag_Actor(actor, text, tp);
}

/**
 * TagPos([tag #])
 */
static int TagPos(MASTER_LIB_CODES operand, int tagno, HPOLYGON hp) {
	int	x, y;

	// Tag could be zero, meaning calling tag
	if (tagno == 0)
		tagno = GetTagPolyId(hp);

	if (operand == TAGTAGXPOS || operand == TAGTAGYPOS) {
		SCNHANDLE junk;

		GetTagTag(GetTagHandle(tagno), &junk, &x, &y);
	} else {
		GetPolyNode(GetTagHandle(tagno), &x, &y);
	}

	if (operand == TAGTAGXPOS || operand == TAGWALKXPOS)
		return x;
	else
		return y;
}

/**
 * Text goes over actor's head while actor plays the talk reel.
 */
static void FinishTalkingReel(CORO_PARAM, PMOVER pMover, int actor) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	if (pMover) {
		SetMoverStanding(pMover);
		AlterMover(pMover, 0, AR_POPREEL);
	} else {
		SetActorTalking(actor, false);
		CORO_INVOKE_ARGS(PlayFilm, (CORO_SUBCTX, GetActorPlayFilm(actor), -1, -1, 0, false, 0, false, 0, false));
	}

	CORO_END_CODE;
}

static void TalkOrSay(CORO_PARAM, SPEECH_TYPE speechType, SCNHANDLE hText, int x, int y,
					  SCNHANDLE hFilm, int actorId, bool bSustain, bool escOn, int myEscape) {
	CORO_BEGIN_CONTEXT;
		int		Loffset, Toffset;	// Top left of display
		int		actor;			// The speaking actor
		PMOVER	pActor;			// For moving actors
		int		myLeftEvent;
		int		escEvents;
		int		ticks;
		bool	bTookControl;	// Set if this function takes control
		bool	bTookTags;		// Set if this function disables tags
		OBJECT	*pText;			// text object pointer
		bool	bSample;		// Set if a sample is playing
		bool	bSamples;
		bool	bTalkReel;		// Set while talk reel is playing
		Audio::SoundHandle handle;
		int	timeout;

		SPEECH_TYPE whatSort;
		TFTYPE	direction;
		int sub;
		int x, y;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	_ctx->whatSort = speechType;
	_ctx->escEvents = myEscape;
	_ctx->x = x;
	_ctx->y = y;
	_ctx->Loffset = 0;
	_ctx->Toffset = 0;
	_ctx->ticks = 0;
	_ctx->pText = NULL;

	// If waiting is enabled, wait for ongoing scroll
	if (TinselV2 && SysVar(SV_SPEECHWAITS))
		CORO_INVOKE_1(WaitScroll, myEscape);

	// Don't do it if it's not wanted
	if (escOn && myEscape != GetEscEvents())
		return;

	_ctx->myLeftEvent = GetLeftEvents();

	// If this actor is dead, call a stop to the calling process
	if (!TinselV2 && (actorId && !actorAlive(actorId)))
		CORO_KILL_SELF();

	if (!TinselV2 || (speechType == IS_TALK)) {
		/*
		 * Find out which actor is talking
		 * and with which direction if no film supplied
		 */
		switch (hFilm) {
		case TF_NONE:
		case TF_UP:
		case TF_DOWN:
		case TF_LEFT:
		case TF_RIGHT:
			_ctx->actor = GetLeadId();	// If no film, actor is lead actor
			_ctx->direction = (TFTYPE)hFilm;
			break;

		default:
			_ctx->actor = ExtractActor(hFilm);
			assert(_ctx->actor); // talk() - no actor ID in the reel
			_ctx->direction = TF_FILM;
			break;
		}
		assert(_ctx->actor);
	} else if (TinselV2)
		_ctx->actor = actorId;

	/*
	 * Lock out the user (for lead actor, if not already locked out)
	 * May need to disable tags for other actors
	 */
	if (_ctx->actor == GetLeadId() || (TinselV2 && (_ctx->actor == LEAD_ACTOR)))
		_ctx->bTookControl = GetControl(CONTROL_OFF);
	else
		_ctx->bTookControl = false;
	_ctx->bTookTags = DisableTagsIfEnabled();

	if (TinselV2) {
		/*
		 * Divert stuff
		 */
		if (SysVar(ISV_DIVERT_ACTOR) && (_ctx->actor == GetLeadId() || _ctx->actor == LEAD_ACTOR)) {
			_ctx->actor = SysVar(ISV_DIVERT_ACTOR);
			if (_ctx->whatSort == IS_TALK)
				_ctx->whatSort = IS_SAY;
			else if (_ctx->whatSort == IS_TALKAT)
				_ctx->whatSort = IS_SAYAT;
		}
	}

	/*
	 * Kick off the voice sample
	 */
	if (_vm->_config->_voiceVolume != 0 && _vm->_sound->sampleExists(hText)) {
		if (!TinselV2) {
			_vm->_sound->playSample(hText, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
			_ctx->bSamples = _vm->_mixer->isSoundHandleActive(_ctx->handle);
		} else
			_ctx->bSamples = true;
	} else
		_ctx->bSamples = false;

	/*
	 * Replace actor with the talk reel, saving the current one
	 */
	_ctx->pActor = GetMover(_ctx->actor);
	if (_ctx->whatSort == IS_TALK) {
		if (_ctx->pActor) {
			if (_ctx->direction != TF_FILM)
				hFilm = GetMoverTalkReel(_ctx->pActor, _ctx->direction);
			AlterMover(_ctx->pActor, hFilm, AR_PUSHREEL);
		} else {
			SetActorTalking(_ctx->actor, true);
			SetActorTalkFilm(_ctx->actor, hFilm);
			CORO_INVOKE_ARGS(PlayFilm, (CORO_SUBCTX, hFilm, -1, -1, 0, false, 0, escOn, myEscape, false));
		}
		_ctx->bTalkReel = true;
		CORO_SLEEP(1);		// Allow the play to come in

	} else if (_ctx->whatSort == IS_TALKAT) {
		_ctx->bTalkReel = false;

	} else if ((_ctx->whatSort == IS_SAY) || (_ctx->whatSort == IS_SAYAT)) {
		_ctx->bTalkReel = false;
		if (IsTaggedActor(_ctx->actor)) {
			CORO_INVOKE_ARGS(ActorEvent, (CORO_SUBCTX, _ctx->actor, TALKING, false, 0));
		} else if (IsTagPolygon(_ctx->actor | ACTORTAG_KEY)) {
			CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, GetTagHandle(_ctx->actor | ACTORTAG_KEY),
				TALKING, 0, false, 0));
		}

		if (TinselV2)
			// Let it all kick in and position this 'waiting' process
			// down the process list from the playing process(es)
			// This ensures immediate return when the reel finishes
			CORO_GIVE_WAY;
	}

	// Make multi-ones escape
	if (TinselV2 && (SubStringCount(hText) > 1) && !_ctx->escEvents)
		_ctx->escEvents = GetEscEvents();

	for (_ctx->sub = 0; _ctx->sub < (TinselV2 ? SubStringCount(hText) : 1); _ctx->sub++) {
		if (TinselV2 && _ctx->escEvents && _ctx->escEvents != GetEscEvents())
			break;

		/*
		 * Display the text.
		 */
		_ctx->bSample = _ctx->bSamples;
		_ctx->pText = NULL;

		if (isJapanMode()) {
			_ctx->ticks = JAP_TEXT_TIME;
		} else if (_vm->_config->_useSubtitles || !_ctx->bSample) {
			/*
			 * Work out where to display the text
			 */
			int	xshift, yshift;

			PlayfieldGetPos(FIELD_WORLD, &_ctx->Loffset, &_ctx->Toffset);
			if ((_ctx->whatSort == IS_SAY) || (_ctx->whatSort == IS_TALK))
				GetActorMidTop(_ctx->actor, &_ctx->x, &_ctx->y);

			if (!TinselV0)
				SetTextPal(GetActorRGB(_ctx->actor));
			if (TinselV2)
				LoadSubString(hText, _ctx->sub, TextBufferAddr(), TBUFSZ);
			else {
				LoadStringRes(hText, TextBufferAddr(), TBUFSZ);

				_ctx->y -= _ctx->Toffset;
			}

			_ctx->pText = ObjectTextOut(coroParam, GetPlayfieldList(FIELD_STATUS),
					TextBufferAddr(), 0, _ctx->x - _ctx->Loffset, _ctx->y - _ctx->Toffset,
					GetTalkFontHandle(), TXT_CENTRE);
			assert(_ctx->pText); // talk() string produced NULL text;

			if (IsTopWindow())
				MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT);

			if ((_ctx->whatSort == IS_SAY) || (_ctx->whatSort == IS_TALK)) {
				/*
				 * Set bottom of text just above the speaker's head
				 * But don't go off the top of the screen
				 */
				if (TinselV2)
					MultiMoveRelXY(_ctx->pText, 0, _ctx->y - _ctx->Toffset - MultiLowest(_ctx->pText) - 2);
				else {
					yshift = _ctx->y - MultiLowest(_ctx->pText) - 2;		// Just above head
					MultiMoveRelXY(_ctx->pText, 0, yshift);		//
					yshift = MultiHighest(_ctx->pText);
					if (yshift < 4)
						MultiMoveRelXY(_ctx->pText, 0, 4 - yshift);	// Not off top

					/*
					 * Don't go off the side of the screen
					 */
					xshift = MultiRightmost(_ctx->pText) + 2;
					if (xshift >= SCREEN_WIDTH)			// Not off right
						MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0);
					xshift = MultiLeftmost(_ctx->pText) - 1;
					if (xshift <= 0)					// Not off left
						MultiMoveRelXY(_ctx->pText, -xshift, 0);
				}
			}

			if (TinselV2)
				// Don't go off the screen
				KeepOnScreen(_ctx->pText, &_ctx->x, &_ctx->y);

			/*
			 * Work out how long to talk.
			 * During this time, reposition the text if the screen scrolls.
			 */
			_ctx->ticks = TextTime(TextBufferAddr());
		}

		if (TinselV2 && _ctx->bSample) {
			// Kick off the sample now (perhaps with a delay)
			if (bNoPause)
				bNoPause = false;
			else
				CORO_SLEEP(SysVar(SV_SPEECHDELAY));

			//SamplePlay(VOICE, hText, _ctx->sub, false, -1, -1, PRIORITY_TALK);
			_vm->_sound->playSample(hText, _ctx->sub, false, -1, -1, PRIORITY_TALK, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
		}

		_ctx->timeout = SAMPLETIMEOUT;

		do {
			// Keep text in place if scrolling
			if (_ctx->pText != NULL) {
				int	nLoff, nToff;

				PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff);
				if (nLoff != _ctx->Loffset || nToff != _ctx->Toffset) {
					MultiMoveRelXY(_ctx->pText, _ctx->Loffset - nLoff, _ctx->Toffset - nToff);
					_ctx->Loffset = nLoff;
					_ctx->Toffset = nToff;
				}
			}

			CORO_SLEEP(1);

			// Handle timeout decrementing and Escape presses
			if (TinselV2) {
				if ((_ctx->escEvents && _ctx->escEvents != GetEscEvents()) ||
					(!bSustain && LeftEventChange(_ctx->myLeftEvent)) ||
					(--_ctx->timeout <= 0)) {
					// Left event only kills current sub-string
					_ctx->myLeftEvent = GetLeftEvents();
					break;
				}
			} else {
				--_ctx->timeout;

				// Abort if escapable and ESCAPE is pressed
				// Abort if left click - hardwired feature for talk!
				// Abort if sample times out
				if ((escOn && myEscape != GetEscEvents())
						|| (_ctx->myLeftEvent != GetLeftEvents())
						|| (_ctx->timeout <= 0))
					break;
			}

			if (_ctx->bSample) {
				// Wait for sample to end whether or not
				if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
					if (_ctx->pText == NULL || _vm->_config->_textSpeed == DEFTEXTSPEED) {
						// No text or speed modification - just depends on sample
						break;
					} else {
						// Talk reel stops at end of speech
						if (!TinselV2 || (_ctx->bTalkReel && (_ctx->sub == SubStringCount(hText) - 1))) {
							CORO_INVOKE_2(FinishTalkingReel, _ctx->pActor, _ctx->actor);
							_ctx->bTalkReel = false;
						}
						_ctx->bSample = false;
					}
				}

				// Decrement the subtitles timeout counter
				if (_ctx->ticks > 0) --_ctx->ticks;

			} else {
				// No sample - just depends on time
				if (_ctx->ticks-- <= 0)
					break;
			}
		} while (1);

		if (_ctx->pText != NULL) {
			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
			_ctx->pText = NULL;
		}
		if (TinselV2 && _ctx->bSample)
			_vm->_sound->stopSpecSample(hText, _ctx->sub);
	}

	/*
	 * The talk is over now - dump the text
	 * Stop the sample
	 * Restore the actor's film or standing reel
	 */
	if (_ctx->bTalkReel)
		CORO_INVOKE_2(FinishTalkingReel, _ctx->pActor, _ctx->actor);
	if (_ctx->pText != NULL)
		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);

	if (TinselV2) {
		if ((_ctx->whatSort == IS_SAY) || (_ctx->whatSort == IS_SAYAT)) {
			SetActorTalking(_ctx->actor, false);
			if (IsTaggedActor(_ctx->actor))
				CORO_INVOKE_ARGS(ActorEvent, (CORO_SUBCTX, _ctx->actor, ENDTALK, false, 0));
			else if (IsTagPolygon(_ctx->actor | ACTORTAG_KEY))
				CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX,
					GetTagHandle(_ctx->actor | ACTORTAG_KEY), ENDTALK, 0, false, 0));

			CORO_SLEEP(1);
		}
	} else {
		_vm->_mixer->stopHandle(_ctx->handle);
	}

	/*
	 * Restore user control and tags, as appropriate
	 * And, finally, release the talk token.
	 */
	if (_ctx->bTookControl) {
		if (TinselV2) ControlOn(); else Control(CONTROL_ON);
	}
	if (_ctx->bTookTags)
		EnableTags();

	CORO_END_CODE;
}

/**
 * TalkAt(actor, x, y, text)
 */
static void TalkAt(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, bool escOn, int myEscape) {
	if (!coroParam) {
		// Don't do it if it's not wanted
		if (escOn && myEscape != GetEscEvents())
			return;

		if (!isJapanMode() && (_vm->_config->_useSubtitles || !_vm->_sound->sampleExists(text)))
			SetTextPal(GetActorRGB(actor));
	}

	Print(coroParam, x, y, text, 0, false, escOn, myEscape);
}

/**
 * TalkAtS(actor, x, y, text, sustain)
 */
static void TalkAtS(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, int sustain, bool escOn, int myEscape) {
	if (!coroParam) {
		assert(sustain == 2);

		// Don't do it if it's not wanted
		if (escOn && myEscape != GetEscEvents())
			return;

		if (!isJapanMode())
			SetTextPal(GetActorRGB(actor));
	}

	Print(coroParam, x, y, text, 0, sustain == 2, escOn, myEscape);
}

/**
 * Set talk font's palette entry.
 */
static void TalkAttr(int r1, int g1, int b1, bool escOn, int myEscape) {
	if (isJapanMode())
		return;

	// Don't do it if it's not wanted
	if (escOn && myEscape != GetEscEvents())
		return;

	if (r1 > MAX_INTENSITY)	r1 = MAX_INTENSITY;	// } Ensure
	if (g1 > MAX_INTENSITY)	g1 = MAX_INTENSITY;	// } within limits
	if (b1 > MAX_INTENSITY)	b1 = MAX_INTENSITY;	// }

	SetTextPal(TINSEL_RGB(r1, g1, b1));
}

/**
 * TalkPaletteIndex
 */
static void TalkPaletteIndex(unsigned index) {
	assert(index);

	SetTalkTextOffset(index);
}

/**
 * Set talk font's palette entry.
 */
static void TalkRGB(COLORREF colour, int myescEvent) {
	// Don't do it if it's not wanted
	if (myescEvent && myescEvent != GetEscEvents())
		return;

	SetTextPal(colour);
}

/**
 * TalkVia("actor"/off)
 */
static void TalkVia(int actor) {
	SetSysVar(ISV_DIVERT_ACTOR, actor);
}

/**
 * Declare a temporary text font.
 */
static void TempTagFont(SCNHANDLE hFilm) {
	SetTempTagFontHandle(hFilm);	// Store the font handle
}

/**
 * Declare a temporary text font.
 */
static void TempTalkFont(SCNHANDLE hFilm) {
	SetTempTalkFontHandle(hFilm);	// Store the font handle
}

/**
 * ThisObject
 */
static int ThisObject(INV_OBJECT *pinvo) {
	assert(pinvo != NULL);

	return pinvo->id;
}

/**
 * ThisTag
 */
static int ThisTag(HPOLYGON hp) {
	int tagno;

	assert(hp != NOPOLY);

	tagno = GetTagPolyId(hp);

	assert(IsTagPolygon(tagno));
	assert(tagno);

	return tagno;
}

/**
 * Get a timer's current count.
 */
static int TimerFn(int timerno) {
	return Timer(timerno);
}

/**
 * Return the icon that caused the CONVERSE event.
 */
int Topic() {
	return GetIcon();
}

/**
 * topplay(film, x, y, actor, hold, complete)
 */
static void TopPlay(CORO_PARAM, SCNHANDLE film, int x, int y, int complete, int actorid, bool splay, int sfact, bool escOn, int myescTime) {
	Play(coroParam, film, x, y, complete, actorid, splay, sfact, escOn, myescTime, true);
}
static void TopPlay(CORO_PARAM, SCNHANDLE hFilm, int x, int y, bool bComplete, int myescEvent, TINSEL_EVENT event) {
	Play(coroParam, hFilm, x, y, bComplete, myescEvent, true, event, NOPOLY, 0);
}

/**
 * Open or close the 'top window'
 */
static void TopWindow(int bpos) {
	bool isStart = (TinselV2 && (bpos != 0)) || (!TinselV2 && (bpos == TW_START));

	KillInventory();

	if (isStart)
		OpenMenu(TOP_WINDOW);
}

/**
 * TranslucentIndex
 */
static void TranslucentIndex(unsigned index) {
	assert(index <= 255);

	SetTranslucencyOffset(index);
}

/**
 * Play a sample.
 */
static void TryPlaySample(CORO_PARAM, int sample, bool bComplete, bool escOn, int myEscape) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);
	// Don't do it if it's not appropriate
	if (_vm->_sound->sampleIsPlaying()) {
		// return, but prevent Glitter lock-up
		CORO_SLEEP(1);
		return;
	}

	CORO_INVOKE_ARGS(PlaySample, (CORO_SUBCTX, sample, bComplete, escOn, myEscape));
	CORO_END_CODE;
}

/**
 * UnDimMusic
 */
static void UnDimMusic() {
	_vm->_pcmMusic->unDim(true);
}

/**
 * unhookscene
 */
static void UnHookSceneFn() {
	UnHookScene();
}

/**
 * Un-define an actor as tagged.
 */
static void UnTagActorFn(int actor) {
	UnTagActor(actor);
}

/**
 * vibrate
 */
static void Vibrate() {
}

/**
 * waitframe(int actor, int frameNumber)
 */
static void WaitFrame(CORO_PARAM, int actor, int frameNumber, bool escOn, int myEscape) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	while (GetActorSteps(actor) < frameNumber) {
		// Don't do it if it's not wanted
		if (escOn && myEscape != GetEscEvents())
			break;

		CORO_SLEEP(1);
	}

	CORO_END_CODE;
}

/**
 * Return when a key pressed or button pushed.
 */
static void WaitKey(CORO_PARAM, bool escOn, int myEscape) {
	CORO_BEGIN_CONTEXT;
		int	startEvent;
		int startX, startY;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	// Don't do it if it's not wanted
	if (escOn && myEscape != GetEscEvents())
		return;

	for (;;) {
		_ctx->startEvent = getUserEvents();
		if (TinselV1) {
			// Store cursor position
			while (!GetCursorXYNoWait(&_ctx->startX, &_ctx->startY, false))
				CORO_SLEEP(1);
		}

		while (_ctx->startEvent == getUserEvents()) {
			CORO_SLEEP(1);

			// Not necessary to monitor escape as it's an event anyway
			if (TinselV1) {
				int curX, curY;
				GetCursorXY(&curX, &curY, false);	// Store cursor position
				if (curX != _ctx->startX || curY != _ctx->startY)
					break;
			}

			if (MenuActive())
				break;
		}

		if (!MenuActive())
			return;

		do {
			CORO_SLEEP(1);
		} while (MenuActive());

		CORO_SLEEP(ONE_SECOND / 2);		// Let it die down
	}
	CORO_END_CODE;
}

/**
 * Return when no scrolling is going on.
 */
void WaitScroll(CORO_PARAM, int myescEvent) {
	CORO_BEGIN_CONTEXT;
		int time;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	// wait for ongoing scroll
	while (IsScrolling()) {
		if (myescEvent && myescEvent != GetEscEvents())
			break;

		CORO_SLEEP(1);
	}

	CORO_END_CODE;
}

/**
 * Pause for requested time.
 */
static void WaitTime(CORO_PARAM, int time, bool frame, bool escOn, int myEscape) {
	CORO_BEGIN_CONTEXT;
		int time;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	// Don't do it if it's not wanted
	if (escOn && myEscape != GetEscEvents())
		return;

	if (!frame)
		time *= ONE_SECOND;

	_ctx->time = time;
	do {
		CORO_SLEEP(1);

		// Abort if escapable and ESCAPE is pressed
		if (escOn && myEscape != GetEscEvents())
			break;
	} while (_ctx->time--);

	CORO_END_CODE;
}

/**
 * Set a moving actor off on a walk.
 */
void Walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE hFilm, int hold, bool igPath,
		  int zOverride, bool escOn, int myescEvent) {
	CORO_BEGIN_CONTEXT;
		int thisWalk;
	CORO_END_CONTEXT(_ctx);

	bool bQuick = hold != 0;
	PMOVER pMover = GetMover(actor);

	assert(pMover); // Can't walk a non-moving actor

	CORO_BEGIN_CODE(_ctx);

	// Straight there if escaped
	if (escOn && myescEvent != GetEscEvents()) {
		if (TinselV2)
			StopMover(pMover);
		CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x, y, 0));
		return;
	}

	if (TinselV2) {
		if (MoverHidden(pMover))
			return;

		// Test 10/10/96
		while (!MoverIs(pMover))
			CORO_SLEEP(1);
	}

	assert(pMover->hCpath != NOPOLY); // moving actor not in path

	// Croak if he is doing an SWalk()
	if (TinselV2) {
		// Croak if he is doing an SWalk()
		if (MoverIsSWalking(pMover))
			CORO_KILL_SELF();

		_ctx->thisWalk = SetActorDest(pMover, x, y, igPath, hFilm);
		SetMoverZoverride(pMover, zOverride);
		DontScrollCursor();

		if (!bQuick) {
			while (MoverMoving(pMover)) {
				// Straight there if escaped
				if (escOn && myescEvent != GetEscEvents()) {
					StopMover(pMover);
					CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x, y, 0));
					break;
				}

				CORO_SLEEP(1);

				// Die if superceded
				if (_ctx->thisWalk != GetWalkNumber(pMover))
					CORO_KILL_SELF();
			}
		}
	} else {

		GetToken(pMover->actorToken);
		SetActorDest(pMover, x, y, igPath, hFilm);
		DontScrollCursor();

		if (hold == 2) {
			;
		} else {
			while (MoverMoving(pMover)) {
				CORO_SLEEP(1);

				// Straight there if escaped
				if (escOn && myescEvent != GetEscEvents()) {
					CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x, y, 0));
					FreeToken(pMover->actorToken);
					return;
				}
			}
		}

		FreeToken(pMover->actorToken);
	}

	CORO_END_CODE;
}

/**
 * Set a moving actor off on a walk.
 * Wait to see if its aborted or completed.
 */
static void Walked(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, bool escOn, int myEscape, bool &retVal) {
	// COROUTINE
	CORO_BEGIN_CONTEXT;
		int	thisWalk;
	CORO_END_CONTEXT(_ctx);

	PMOVER pMover = GetMover(actor);
	assert(pMover); // Can't walk a non-moving actor

	CORO_BEGIN_CODE(_ctx);

	// Straight there if escaped
	if (escOn && myEscape != GetEscEvents()) {
		CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x, y, 0));
		retVal = true;
		return;
	}

	if (TinselV2) {
		if (MoverHidden(pMover) || !MoverIs(pMover)) {
			retVal = false;
			return;
		}
		assert(pMover->hCpath != NOPOLY); // moving actor not in path

		// Not if he is doing an SWalk()
		if (MoverIsSWalking(pMover)) {
			retVal = false;
			return;
		}

	} else {
		// Pause before starting the walk
		CORO_SLEEP(ONE_SECOND);

		assert(pMover->hCpath != NOPOLY); // moving actor not in path

		// Briefly aquire token to kill off any other normal walk
		GetToken(pMover->actorToken);
		FreeToken(pMover->actorToken);
	}

	_ctx->thisWalk = SetActorDest(pMover, x, y, false, film);
	DontScrollCursor();

	while (MoverMoving(pMover) && (_ctx->thisWalk == GetWalkNumber(pMover))) {
		// Straight there if escaped
		if (escOn && myEscape != GetEscEvents()) {
			CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x, y, 0));
			retVal = true;
			return;
		}

		CORO_SLEEP(1);
	}

	int	endx, endy;
	GetMoverPosition(pMover, &endx, &endy);
	retVal = (_ctx->thisWalk == GetWalkNumber(pMover) && endx == x && endy == y);

	CORO_END_CODE;
}

/**
 * Declare a moving actor.
 */
static void WalkingActor(uint32 id, SCNHANDLE *rp = NULL) {
	PMOVER	pActor;		// Moving actor structure

	if (TinselVersion == TINSEL_V2) {
		RegisterMover(id);
		return;
	}

	RegisterMover(id);		// Establish as a moving actor
	pActor = GetMover(id);
	assert(pActor);

	// Store all those reels
	int i, j;
	for (i = 0; i < 5; ++i) {
		for (j = 0; j < 4; ++j)
			pActor->walkReels[i][j] = *rp++;
		for (j = 0; j < 4; ++j)
			pActor->standReels[i][j] = *rp++;
	}


	for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) {
		for (j = 0; j < 4; ++j) {
			pActor->walkReels[i][j] = pActor->walkReels[4][j];
			pActor->standReels[i][j] = pActor->standReels[2][j];
		}
	}
}

/**
 * Walk a moving actor towards the polygon's tag, but return when the
 * actor enters the polygon.
 */
static void WalkPoly(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myEscape) {
	int	pnodex, pnodey;

	// COROUTINE
	CORO_BEGIN_CONTEXT;
		int thisWalk;
	CORO_END_CONTEXT(_ctx);

	assert(hp != NOPOLY); // WalkPoly() may only be called from a polygon code block
	PMOVER pMover = GetMover(actor);
	assert(pMover); // Can't walk a non-moving actor

	CORO_BEGIN_CODE(_ctx);

	// Straight there if escaped
	if (escOn && myEscape != GetEscEvents()) {
		StandTag(actor, hp);
		return;
	}

	if (TinselV2) {
		if (MoverHidden(pMover))
			return;

		// Croak if he is doing an SWalk()
		if (MoverIsSWalking(pMover))
			CORO_KILL_SELF();

	} else {
		GetToken(pMover->actorToken);
	}

	GetPolyNode(hp, &pnodex, &pnodey);
	_ctx->thisWalk = SetActorDest(pMover, pnodex, pnodey, false, film);
	DoScrollCursor();

	while (!MoverIsInPolygon(pMover, hp) && MoverMoving(pMover)) {
		CORO_SLEEP(1);

		if (escOn && myEscape != GetEscEvents()) {
			// Straight there if escaped
			StandTag(actor, hp);
			if (!TinselV2)
				FreeToken(pMover->actorToken);
			return;
		}

		// Die if superceded
		if (TinselV2 && (_ctx->thisWalk != GetWalkNumber(pMover)))
			CORO_KILL_SELF();
	}

	if (!TinselV2)
		FreeToken(pMover->actorToken);

	CORO_END_CODE;
}

/**
 * WalkTag(actor, reel, hold)
 */
static void WalkTag(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myEscape) {
	// COROUTINE
	CORO_BEGIN_CONTEXT;
		int thisWalk;
	CORO_END_CONTEXT(_ctx);

	PMOVER pMover = GetMover(actor);
	assert(pMover); // Can't walk a non-moving actor

	CORO_BEGIN_CODE(_ctx);

	int	pnodex, pnodey;

	assert(hp != NOPOLY); // walkpoly() may only be called from a polygon code block

	// Straight there if escaped
	if (escOn && myEscape != GetEscEvents()) {
		StandTag(actor, hp);
		return;
	}

	if (!TinselV2)
		GetToken(pMover->actorToken);
	else {
		if (MoverHidden(pMover))
			return;
	}

	GetPolyNode(hp, &pnodex, &pnodey);

	_ctx->thisWalk = SetActorDest(pMover, pnodex, pnodey, false, film);
	DoScrollCursor();

	while (MoverMoving(pMover)) {
		if (escOn && myEscape != GetEscEvents()) {
			// Straight there if escaped
			StandTag(actor, hp);
			if (!TinselV2)
				FreeToken(pMover->actorToken);
			return;
		}

		CORO_SLEEP(1);

		// Die if superceded
		if (TinselV2 && (_ctx->thisWalk != GetWalkNumber(pMover)))
			CORO_KILL_SELF();
	}

	// Adopt the tag-related reel
	SCNHANDLE pFilm = GetPolyFilm(hp);

	switch (pFilm) {
	case TF_NONE:
		break;

	case TF_UP:
		SetMoverDirection(pMover, AWAY);
		SetMoverStanding(pMover);
		break;
	case TF_DOWN:
		SetMoverDirection(pMover, FORWARD);
		SetMoverStanding(pMover);
		break;
	case TF_LEFT:
		SetMoverDirection(pMover, LEFTREEL);
		SetMoverStanding(pMover);
		break;
	case TF_RIGHT:
		SetMoverDirection(pMover, RIGHTREEL);
		SetMoverStanding(pMover);
		break;

	default:
		if (actor == LEAD_ACTOR || actor == GetLeadId())
			AlterMover(pMover, pFilm, AR_NORMAL);
		else
			SetMoverStanding(pMover);
		break;
	}

	if (!TinselV2)
		FreeToken(pMover->actorToken);

	CORO_END_CODE;
}

/**
 * Returns the X co-ordinateof lead actor's last walk.
 */
int WalkXPos() {
	return GetLastLeadXdest();
}

/**
 * Returns the Y co-ordinateof lead actor's last walk.
 */
int WalkYPos() {
	return GetLastLeadYdest();
}

/**
 * Return which is the current CD, counting from 1.
 */
int WhichCd() {
	return GetCurrentCD();
}

/**
 * whichinventory
 */
int WhichInventory() {
	return WhichInventoryOpen();
}


/**
 * Subtract one less that the number of parameters from pp
 * pp then points to the first parameter.
 *
 * If the library function has no return value:
 * return -(the number of parameters) to pop them from the stack
 *
 * If the library function has a return value:
 * return -(the number of parameters - 1) to pop most of them from
 * the stack, and stick the return value in pp[0]
 * @param operand			Library function
 * @param pp				Top of parameter stack
 */
int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pic, RESUME_STATE *pResumeState) {
	int libCode;
	if (TinselV0) libCode = DW1DEMO_CODES[operand];
	else if (!TinselV2) libCode = DW1_CODES[operand];
	else libCode = DW2_CODES[operand];

	debug(7, "CallLibraryRoutine op %d (escOn %d, myEscape %d)", operand, pic->escOn, pic->myEscape);
	switch (libCode) {
	case ACTORATTR:
		// DW1 only
		pp -= 3;			// 4 parameters
		ActorAttr(pp[0], pp[1], pp[2], pp[3]);
		return -4;

	case ACTORBRIGHTNESS:
		// DW2 only
		pp -= 1;
		ActorBrightness(pp[0], pp[1]);
		return -2;

	case ACTORDIRECTION:
		// Common to both DW1 & DW2
		pp[0] = ActorDirection(pp[0]);
		return 0;

	case ACTORPALETTE:
		// DW2 only
		pp -= 2;			// 3 parameters
		ActorPalette(pp[0], pp[1], pp[2]);
		return -3;

	case ACTORPRIORITY:
		// DW2 only
		pp -= 1;			// 2 parameters
		ActorPriority(pp[0], pp[1]);
		return -2;

	case ACTORREF:
		// Common to both DW1 & DW2
		if (!TinselV0)
			error("actorref isn't a real function");
		return 0;

	case ACTORRGB:
		// DW2 only
		pp -= 1;			// 2 parameters
		ActorRGB(pp[0], pp[1]);
		return -2;

	case ACTORSCALE:
		// Common to both DW1 & DW2
		pp[0] = ActorScale(pp[0]);
		return 0;

	case ACTORSON:
		// DW1 only
		ActorsOn();
		return 0;

	case ACTORXPOS:
		// Common to both DW1 & DW2
		pp[0] = ActorPos(ACTORXPOS, pp[0]);
		return 0;

	case ACTORYPOS:
		// Common to both DW1 & DW2
		pp[0] = ActorPos(ACTORYPOS, pp[0]);
		return 0;

	case ADDHIGHLIGHT:
		// DW2 only
		// Command doesn't actually do anything
		pp -= 1;			// 2 parameters
		return -2;

	case ADDINV:
		// DW2 only
		AddInv(INV_DEFAULT, pp[0]);
		return -1;

	case ADDINV1:
		// Common to both DW1 & DW2
		AddInv(INV_1, pp[0]);
		return -1;

	case ADDINV2:
		// Common to both DW1 & DW2
		AddInv(INV_2, pp[0]);
		return -1;

	case ADDOPENINV:
		// Common to both DW1 & DW2
		AddInv(TinselV2 ? DW2_INV_OPEN : INV_OPEN, pp[0]);
		return -1;

	case ADDTOPIC:
		// Common to both DW1 & DW2
		AddTopic(pp[0]);
		return -1;

	case AUXSCALE:
		// DW1 only
		pp -= 13;			// 14 parameters
		AuxScale(pp[0], pp[1], (SCNHANDLE *)(pp+2));
		return -14;

	case BACKGROUND:
		// Common to both DW1 & DW2
		Background(coroParam, pp[0]);
		return -1;

	case BLOCKING:
		// DW2 only
		Blocking(pp[0]);
		return -1;

	case CALLACTOR:
	case CALLGLOBALPROCESS:
	case CALLOBJECT:
		// DW2 only
		pp -= 1;			// 2 parameters
		if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
			bool result;
			*pResumeState = RES_NOT;
			FinishWaiting(coroParam, pic, &result);
			if (coroParam) {
				*pResumeState = RES_1;
				return 0;
			}
			pp[0] = result ? 1 : 0;
		} else {
			uint32 v;
			if (libCode == CALLACTOR)
				v = SendActor(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->hPoly, pic->myEscape);
			else if (libCode == CALLGLOBALPROCESS)
				v = SendGlobalProcess(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape);
			else
				v = SendObject(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape);

			if (coroParam)
				return 0;
			pp[0] = v;
		}

		if (!pp[0])
			KillSelf(coroParam);
		return -2;


	case CALLPROCESS:
		// DW2 only
		pp -= 1;			// 2 parameters
		if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
			bool result;
			*pResumeState = RES_NOT;
			FinishWaiting(coroParam, pic, &result);
			if (coroParam) {
				*pResumeState = RES_1;
				return 0;
			}

			pp[0] = result ? 1 : 0;
		} else {
			int result = SendProcess(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape);
			if (coroParam)
				return 0;

			pp[0] = result;
		}
		return -2;

	case CALLSCENE:
		// DW2 only
		error("CallScene isn't a real function");

	case CALLTAG:
		// DW2 only
		pp -= 1;			// 2 parameters
		if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
			bool result;
			*pResumeState = RES_NOT;
			FinishWaiting(coroParam, pic, &result);
			if (coroParam) {
				*pResumeState = RES_1;
				return 0;
			}

			pp[0] = result ? 1 : 0;
		} else {
			bool result;
			SendTag(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->hPoly, pic->myEscape, &result);
			if (coroParam)
				return 0;

			pp[0] = result ? 1 : 0;
		}

		if (!pp[0])
			KillSelf(coroParam);
		return -2;

	case CAMERA:
		// Common to both DW1 & DW2
		Camera(pp[0]);
		return -1;

	case CDCHANGESCENE:
		// DW2 only
		CdChangeScene(pp[0]);
		return -1;

	case CDDOCHANGE:
		// DW2 only
		CdDoChange(coroParam);
		return 0;

	case CDENDACTOR:
		// DW2 only
		CdEndActor(pp[0], pic->myEscape);
		return -1;

	case CDLOAD:
		// Common to both DW1 & DW2
		pp -= 1;			// 2 parameters
		CDload(pp[0], pp[1], pic->myEscape);
		return -2;

	case CDPLAY:
		// Common to both DW1 & DW2
		error("cdplay isn't a real function");

	case CLEARHOOKSCENE:
		// Common to both DW1 & DW2
		ClearHookScene();
		return 0;

	case CLOSEINVENTORY:
		// Common to both DW1 & DW2
		CloseInventory();
		return 0;

	case CONTROL:
		// Common to both DW1 & DW2
		Control(pp[0]);
		return -1;

	case CONVERSATION:
		// Common to both DW1 & DW2
		Conversation(coroParam, pp[0], pic->hPoly, pic->idActor, pic->escOn, pic->myEscape);
		return -1;

	case CONVTOPIC:
		// Common to both DW1 & DW2
		ConvTopic(pp[0]);
		return -1;

	case CURSOR:
		// DW2 only
		Cursor(pp[0]);
		return -1;

	case CURSORXPOS:
		// Common to both DW1 & DW2
		pp[0] = CursorPos(CURSORXPOS);
		return 0;

	case CURSORYPOS:
		// Common to both DW1 & DW2
		pp[0] = CursorPos(CURSORYPOS);
		return 0;

	case CUTSCENE:
		// DW1 only
		error("cutscene isn't a real function");

	case DECCONVW:
		// Common to both DW1 & DW2
		pp -= 7;			// 8 parameters
		DecConvW(pp[0], pp[1], pp[2], pp[3],
			 pp[4], pp[5], pp[6], pp[7]);
		return -8;

	case DECCSTRINGS:
		// DW1 only
		pp -= 19;			// 20 parameters
		DecCStrings((SCNHANDLE *)pp);
		return -20;

	case DECCURSOR:
		// Common to both DW1 & DW2
		DecCursor(pp[0]);
		return -1;

	case DECFLAGS:
		// Common to both DW1 & DW2
		if (TinselV2)
			error("DecFlags() is obsolete");

		DecFlags(pp[0]);
		return -1;

	case DECINV1:
		// Common to both DW1 & DW2
		pp -= 7;			// 8 parameters
		DecInv1(pp[0], pp[1], pp[2], pp[3],
			 pp[4], pp[5], pp[6], pp[7]);
		return -8;

	case DECINV2:
		// Common to both DW1 & DW2
		pp -= 7;			// 8 parameters
		DecInv2(pp[0], pp[1], pp[2], pp[3],
			 pp[4], pp[5], pp[6], pp[7]);
		return -8;

	case DECINVW:
		// Common to both DW1 & DW2
		DecInvW(pp[0]);
		return -1;

	case DECLARELANGUAGE:
		// DW2 only
		pp -= 2;			// 3 parameters
		DeclareLanguage(pp[0], pp[1], pp[2]);
		return -3;

	case DECLEAD:
		// Common to both DW1 & DW2
		if (TinselV2) {
			DecLead(pp[0]);
			return -1;
		} else {
			pp -= 61;			// 62 parameters
			DecLead(pp[0], (SCNHANDLE *)&pp[1], pp[61]);
			return -62;
		}

	case DECSCALE:
		// DW2 only
		pp -= 13;			// 14 parameters
		DecScale(pp[0], pp[1], pp[2], pp[3], pp[4],
			 pp[5], pp[6], pp[7], pp[8], pp[9],
			 pp[10], pp[11], pp[12], pp[13]);
		return -14;

	case DECTAGFONT:
		// Common to both DW1 & DW2
		DecTagFont(pp[0]);
		return -1;

	case DECTALKFONT:
		// Common to both DW1 & DW2
		DecTalkFont(pp[0]);
		return -1;

	case DELICON:
		// DW1 only
		DelIcon(pp[0]);
		return -1;

	case DELINV:
		// DW1 only
		DelInv(pp[0]);
		return -1;

	case DELTOPIC:
		// DW2 only
		DelTopic(pp[0]);
		return -1;

	case DIMMUSIC:
		// DW2 only
		DimMusic();
		return 0;

	case DROP:
		// DW2 only
		Drop(pp[0]);
		return -1;

	case DROPEVERYTHING:
		// DW2 only
		DropEverything();
		return 0;

	case DROPOUT:
		// DW1 only
		error("DropOut (%d)", pp[0]);

	case EFFECTACTOR:
		// Common to both DW1 & DW2
		assert(pic->event == WALKIN || pic->event == WALKOUT); // effectactor() must be from effect poly code

		pp[0] = pic->idActor;
		return 0;

	case ENABLEMENU:
		// Common to both DW1 & DW2
		EnableMenu();
		return 0;

	case ENDACTOR:
		// DW2 only
		EndActor(pp[0]);
		return -1;

	case ESCAPE:
	case ESCAPEOFF:
	case ESCAPEON:
		// Common to both DW1 & DW2
		error("Escape isn't a real function");

	case EVENT:
		// Common to both DW1 & DW2
		if (TinselVersion == TINSEL_V2)
			pp[0] = pic->event;
		else
			pp[0] = TINSEL1_EVENT_MAP[pic->event];
		return 0;

	case FACETAG:
		// DW2 only
		FaceTag(pp[0], pic->hPoly);
		return -1;

	case FADEIN:
		// DW2 only
		FadeIn();
		return 0;

	case FADEMIDI:
		// DW1 only
		FadeMidi(coroParam, pp[0]);
		return -1;

	case FADEOUT:
		// DW1 only
		FadeOut();
		return 0;

	case FRAMEGRAB:
		// Common to both DW1 & DW2
		return -1;

	case FREEZECURSOR:
		// DW2 only
		FreezeCursor(pp[0]);
		return -1;

	case GETINVLIMIT:
		// Common to both DW1 & DW2
		pp[0] = GetInvLimit(pp[0]);
		return 0;

	case GHOST:
		// DW2 only
		pp -= 2;			// 3 parameters
		Ghost(pp[0], pp[1], pp[2]);
		return -3;

	case GLOBALVAR:
		// DW1 only
		error("GlobalVar isn't a real function");

	case GRABMOVIE:
		// DW2 only
		return -1;

	case HAILSCENE:
		// DW2 only
		HailScene(pp[0]);
		return -1;

	case HASRESTARTED:
		// Common to both DW1 & DW2
		pp[0] = HasRestarted();
		return 0;

	case HAVE:
		// DW2 only
		pp[0] = Have(pp[0]);
		return 0;			// using return value

	case HELDOBJECT:
		// Common to both DW1 & DW2
		pp[0] = HeldObject();
		return 0;

	case HIDEACTOR:
		// Common to both DW1 & DW2
		if (!TinselV2)
			HideActorFn(coroParam, pp[0]);
		else if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
			*pResumeState = RES_NOT;
			FinishWaiting(coroParam, pic);
			if (coroParam) {
				*pResumeState = RES_1;
				return 0;
			}
		} else
			HideActorFn(coroParam, pp[0]);
		return -1;

	case HIDEBLOCK:
		// DW2 only
		HideBlock(pp[0]);
		return -1;

	case HIDEEFFECT:
		// DW2 only
		HideEffect(pp[0]);
		return -1;

	case HIDEPATH:
		// DW2 only
		HidePath(pp[0]);
		return -1;

	case HIDEREFER:
		// DW2 only
		HideRefer(pp[0]);
		return -1;

	case HIDETAG:
		// DW2 only
		if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
			*pResumeState = RES_NOT;
			FinishWaiting(coroParam, pic);
			if (coroParam) {
				*pResumeState = RES_1;
				return 0;
			}
		} else {
			HideTag(coroParam, pp[0], pic->hPoly);
			if (coroParam)
				return 0;
		}
		return -1;

	case HOLD:
		// DW2 only
		Hold(pp[0]);
		return -1;

	case HOOKSCENE:
		// Common to both DW1 & DW2
		pp -= 2;			// 3 parameters
		HookScene(pp[0], pp[1], pp[2]);
		return -3;

	case IDLETIME:
		// Common to both DW1 & DW2
		pp[0] = IdleTime();
		return 0;

	case ININVENTORY:
		// DW1 only
		pp[0] = InInventory(pp[0]);
		return 0;			// using return value

	case INSTANTSCROLL:
		// DW2 only
		InstantScroll(pp[0]);
		return -1;

	case INVDEPICT:
		// DW1 only
		pp -= 1;			// 2 parameters
		InvDepict(pp[0], pp[1]);
		return -2;

	case INVENTORY:
		// Common to both DW1 & DW2
		Inventory(pp[0], pic->escOn, pic->myEscape);
		return -1;

	case INVPLAY:
		// DW2 only
		pp -= 1;			// 2 parameters
		InvPlay(pp[0], pp[1]);
		return -2;

	case INWHICHINV:
		// Common to both DW1 & DW2
		pp[0] = InWhichInv(pp[0]);
		return 0;			// using return value

	case KILLACTOR:
		// DW1 only
		if (TinselV2)
			error("KillActor() was not expected to be required");

		KillActor(pp[0]);
		return -1;

	case KILLBLOCK:
		// DW1 only
		KillBlock(pp[0]);
		return -1;

	case KILLEXIT:
		// DW1 only
		KillExit(pp[0]);
		return -1;

	case KILLGLOBALPROCESS:
		// DW2 only
		KillGlobalProcess(pp[0]);
		return -1;

	case KILLPROCESS:
		// DW2 only
		KillProcess(pp[0]);
		return -1;

	case KILLTAG:
		// DW1 only
		KillTag(coroParam, pp[0]);
		return -1;

	case LOCALVAR:
		// DW2 only
		error("LocalVar isn't a real function");

	case MOVECURSOR:
		// Common to both DW1 & DW2
		pp -= 1;			// 2 parameters
		MoveCursor(pp[0], pp[1]);
		return -2;

	case MOVETAG:
		// DW2 only
		pp -= 2;			// 3 parameters
		MoveTag(pp[0], pp[1], pp[2], pic->hPoly);
		return -3;

	case MOVETAGTO:
		// DW2 only
		pp -= 2;			// 3 parameters
		MoveTagTo(pp[0], pp[1], pp[2], pic->hPoly);
		return -3;

	case NEWSCENE:
		// Common to both DW1 & DW2
		pp -= 2;			// 3 parameters
		if (*pResumeState == RES_2)
			*pResumeState = RES_NOT;
		else
			NewScene(coroParam, pp[0], pp[1], pp[2]);
		return -3;

	case NOBLOCKING:
		// Common to both DW1 & DW2
		NoBlocking();
		return 0;

	case NOPAUSE:
		// DW2 only
		bNoPause = true;
		return 0;

	case NOSCROLL:
		// Common to both DW1 & DW2
		pp -= 3;			// 4 parameters
		NoScroll(pp[0], pp[1], pp[2], pp[3]);
		return -4;

	case OBJECTHELD:
		// DW1 only
		ObjectHeld(pp[0]);
		return -1;

	case OFFSET:
		// Common to both DW1 & DW2
		if (TinselV2) {
			pp -= 2;			// 2 parameters
			Offset((EXTREME)pp[0], pp[1], pp[2]);
			return -3;
		} else {
			pp -= 1;			// 2 parameters
			Offset(EX_USEXY, pp[0], pp[1]);
			return -2;
		}

	case OTHEROBJECT:
		// DW2 only
		pp[0] = OtherObject(pic->pinvo);
		return 0;

	case PAUSE:
		// DW2 only
		WaitTime(coroParam, 1, true, pic->escOn, pic->myEscape);
		return 0;

	case PLAY:
		// Common to both DW1 & DW2
		if (TinselV2) {
			pp -= 3;			// 4 parameters
			if (*pResumeState == RES_1 && IsCdPlayHandle(pp[0]))
				*pResumeState = RES_NOT;
			else {
				Play(coroParam, pp[0], pp[1], pp[2], pp[3], pic->myEscape, false,
						pic->event, pic->hPoly, pic->idActor);

				if (coroParam)
					return 0;
			}
			return -4;

		} else {
			pp -= 5;			// 6 parameters

			if (pic->event == WALKIN || pic->event == WALKOUT)
				Play(coroParam, pp[0], pp[1], pp[2], pp[5], 0, false, 0, pic->escOn, pic->myEscape, false);
			else
				Play(coroParam, pp[0], pp[1], pp[2], pp[5], pic->idActor, false, 0, pic->escOn, pic->myEscape, false);
			return -6;
		}

	case PLAYMIDI:
		// Common to both DW1 & DW2
		pp -= 2;			// 3 parameters
		PlayMidi(coroParam, pp[0], pp[1], pp[2]);
		return -3;

	case PLAYMOVIE:
		// DW2 only
		PlayMovie(coroParam, pp[0], pic->myEscape);
		return -1;

	case PLAYMUSIC:
		// DW2 only
		PlayMusic(pp[0]);
		return -1;

	case PLAYRTF:
		// Common to both DW1 & DW2
		error("playrtf only applies to cdi");

	case PLAYSAMPLE:
		// Common to both DW1 & DW2
		if (TinselV2) {
			pp -= 3;			// 4 parameters
			PlaySample(coroParam, pp[0], pp[1], pp[2], pp[3], pic->myEscape);
			return -4;
		} else {
			pp -= 1;			// 2 parameters
			PlaySample(coroParam, pp[0], pp[1], pic->escOn, pic->myEscape);
			return -2;
		}

	case POINTACTOR:
		// DW2 only
		PointActor(pp[0]);
		return -1;

	case POINTTAG:
		// DW2 only
		PointTag(pp[0], pic->hPoly);
		return -1;

	case POSTACTOR:
		// DW2 only
		pp -= 1;			// 2 parameters
		PostActor(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->hPoly, pic->idActor, pic->myEscape);
		return -2;

	case POSTGLOBALPROCESS:
		// DW2 only
		pp -= 1;			// 2 parameters
		PostGlobalProcess(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape);
		return -2;

	case POSTOBJECT:
		// DW2 only
		pp -= 1;			// 2 parameters
		PostObject(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape);
		return -2;

	case POSTPROCESS:
		// DW2 only
		pp -= 1;			// 2 parameters
		PostProcess(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape);
		return -2;

	case POSTTAG:
		// DW2 only
		pp -= 1;			// 2 parameters
		PostTag(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->hPoly, pic->myEscape);
		return -2;


	case PREPARESCENE:
		// DW1 only
		PrepareScene(pp[0]);
		return -1;

	case PRINT:
		// Common to both DW1 & DW2
		if (TinselV2) {
			pp -= 4;			// 5 parameters
			Print(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4] != 0, pic->escOn, pic->myEscape);
			return -5;
		} else {
			pp -= 5;			// 6 parameters
			/* pp[2] was intended to be attribute */
			Print(coroParam, pp[0], pp[1], pp[3], pp[4], pp[5] == 2, pic->escOn, pic->myEscape);
			return -6;
		}

	case PRINTCURSOR:
		// DW2 only
		PrintTag(pic->hPoly, pp[0], pic->idActor, true);
		return -1;

	case PRINTOBJ:
		// Common to both DW1 & DW2
		PrintObj(coroParam, pp[0], pic->pinvo, pic->event, pic->myEscape);
		return -1;

	case PRINTTAG:
		// Common to both DW1 & DW2
		PrintTag(pic->hPoly, pp[0], TinselV2 ? pic->idActor : 0, false);
		return -1;

	case QUITGAME:
		// Common to both DW1 & DW2
		QuitGame();
		return 0;

	case RANDOM:
		// Common to both DW1 & DW2
		pp -= 2;			// 3 parameters
		pp[0] = RandomFn(pp[0], pp[1], pp[2]);
		return -2;		// One holds return value

	case RESETIDLETIME:
		// Common to both DW1 & DW2
		ResetIdleTime();
		return 0;

	case RESTARTGAME:
		// Common to both DW1 & DW2
		FnRestartGame();
		return 0;

	case RESTORESCENE:
		// Common to both DW1 & DW2
		if (TinselV2) {
			RestoreScene(coroParam, (TRANSITS)pp[0]);
			return -1;
		} else {
			RestoreScene(coroParam, TRANS_FADE);
			return 0;
		}

	case RESTORE_CUT:
		// DW1 only
		RestoreScene(coroParam, TRANS_CUT);
		return 0;

	case RESUMELASTGAME:
		// DW2 only
		ResumeLastGame();
		return 0;

	case RUNMODE:
		// Common to both DW1 & DW2
		pp[0] = RunMode();
		return 0;

	case SAMPLEPLAYING:
		// DW1 only
		pp[0] = SamplePlaying(pic->escOn, pic->myEscape);
		return 0;

	case SAVESCENE:
		// Common to both DW1 & DW2
		if (*pResumeState == RES_1)
			*pResumeState = RES_2;
		else
			SaveScene(coroParam);
		return 0;

	case SAY:
		// DW2 only
		pp -= 1;			// 2 parameters
		TalkOrSay(coroParam, IS_SAY, pp[1], 0, 0, 0, pp[0], false, pic->escOn, pic->myEscape);
		return -2;

	case SAYAT:
		// DW2 only
		pp -= 4;			// 5 parameters
		TalkOrSay(coroParam, IS_SAYAT, pp[3], pp[1], pp[2], 0, pp[0], pp[4], pic->escOn, pic->myEscape);
		return -5;

	case SCALINGREELS:
		// Common to both DW1 & DW2
		pp -= 6;			// 7 parameters
		ScalingReels(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]);
		return -7;

	case SCANICON:
		// DW1 only
		pp[0] = ScanIcon();
		return 0;

	case SCREENXPOS:
		// Common to both DW1 & DW2
		pp[0] = LToffset(SCREENXPOS);
		return 0;

	case SCREENYPOS:
		// Common to both DW1 & DW2
		pp[0] = LToffset(SCREENYPOS);
		return 0;

	case SCROLL:
		// Common to both DW1 & DW2
		if (TinselV2) {
			pp -= 5;			// 6 parameters
			Scroll(coroParam, (EXTREME)pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pic->escOn, pic->myEscape);
			return -6;
		} else {
			pp -= 3;			// 4 parameters
			Scroll(coroParam, EX_USEXY, pp[0], pp[1], pp[2], pp[2], pp[3], pic->escOn, pic->myEscape);
			return -4;
		}

	case SCROLLPARAMETERS:
		// DW2 only
		pp -= 6;			// 7 parameters
		ScrollParameters(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]);
		return -7;

	case SENDTAG:
		// DW2 only
		pp -= 1;			// 2 parameters
		if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
			bool result;
			*pResumeState = RES_NOT;
			FinishWaiting(coroParam, pic, &result);
			if (coroParam) {
				*pResumeState = RES_1;
				return 0;
			}
			pp[0] = result ? 1 : 0;
		} else {
			bool result;
			SendTag(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->hPoly, pic->myEscape, &result);
			if (coroParam)
				return 0;

			pp[0] = result;
		}
		return -1;


	case SETACTOR:
		// DW1 only
		SetActor(pp[0]);
		return -1;

	case SETBLOCK:
		// DW1 only
		SetBlock(pp[0]);
		return -1;

	case SETEXIT:
		// DW1 only
		SetExit(pp[0]);
		return -1;

	case SETINVLIMIT:
		// Common to both DW1 & DW2
		pp -= 1;			// 2 parameters
		SetInvLimit(pp[0], pp[1]);
		return -2;

	case SETINVSIZE:
		// Common to both DW1 & DW2
		pp -= 6;			// 7 parameters
		SetInvSize(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]);
		return -7;

	case SETLANGUAGE:
		// Common to both DW1 & DW2
		SetLanguage((LANGUAGE)pp[0]);
		return -1;

	case SETPALETTE:
		// Common to both DW1 & DW2
		if (TinselV2) {
			// Note: Although DW2 introduces parameters for start and length, it doesn't use them
			pp -= 2;
			SetPalette(pp[0], pic->escOn, pic->myEscape);
			return -3;
		} else {
			SetPalette(pp[0], pic->escOn, pic->myEscape);
			return -1;
		}

	case SETSYSTEMSTRING:
		// DW2 only
		pp -= 1;				// 2 parameters
		SetSystemString(pp[0], pp[1]);
		return -2;

	case SETSYSTEMVAR:
		// DW1 only
		pp -= 1;				// 2 parameters
		SetSystemVar(pp[0], pp[1]);
		return -2;

	case SETTAG:
		// DW1 only
		SetTag(coroParam, pp[0]);
		return -1;

	case SETTIMER:
		// DW1 only
		pp -= 3;			// 4 parameters
		SetTimer(pp[0], pp[1], pp[2], pp[3]);
		return -4;

	case SHELL:
		// DW2 only
		Shell(pp[0]);
		return 0;

	case SHOWACTOR:
		// DW2 only
		if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
			*pResumeState = RES_NOT;
			FinishWaiting(coroParam, pic);
			if (coroParam) {
				*pResumeState = RES_1;
				return 0;
			}
		} else
			ShowActorFn(coroParam, pp[0]);
		return -1;

	case SHOWBLOCK:
		// DW2 only
		ShowBlock(pp[0]);
		return -1;

	case SHOWEFFECT:
		// DW2 only
		ShowEffect(pp[0]);
		return -1;

	case SHOWMENU:
		// DW2 only
		ShowMenu();
		return 0;

	case SHOWPATH:
		// DW2 only
		ShowPath(pp[0]);
		return -1;

	case SHOWPOS:
		// DW1 only
#ifdef DEBUG
		showpos();
#endif
		return 0;

	case SHOWREFER:
		// DW2 only
		ShowRefer(pp[0]);
		return -1;

	case SHOWSTRING:
#ifdef DEBUG
		showstring();
#endif
		return 0;

	case SHOWTAG:
		// DW2 only
		if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
			*pResumeState = RES_NOT;
			FinishWaiting(coroParam, pic);

			if (coroParam) {
				*pResumeState = RES_1;
				return 0;
			}
		} else {
			ShowTag(coroParam, pp[0], pic->hPoly);
		}
		return -1;

	case SPLAY:
		// DW1 only
		pp -= 6;			// 7 parameters

		if (pic->event == WALKIN || pic->event == WALKOUT)
			SPlay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], 0, pic->escOn, pic->myEscape);
		else
			SPlay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], pic->idActor, pic->escOn, pic->myEscape);
		return -7;

	case STAND:
		// Common to both DW1 & DW2
		pp -= 3;			// 4 parameters
		Stand(coroParam, pp[0], pp[1], pp[2], pp[3]);
		return -4;

	case STANDTAG:
		// Common to both DW1 & DW2
		StandTag(pp[0], pic->hPoly);
		return -1;

	case STARTGLOBALPROCESS:
		// DW2 only
		StartGlobalProcess(coroParam, pp[0]);
		return -1;

	case STARTPROCESS:
		// DW2 only
		StartProcess(coroParam, pp[0]);
		return -1;

	case STARTTIMER:
		// DW2 only
		pp -= 3;			// 4 parameters
		StartTimerFn(pp[0], pp[1], pp[2], pp[3]);
		return -4;

	case STOPMIDI:
		// DW1 only
		StopMidiFn();
		return 0;

	case STOPSAMPLE:
		// Common to both DW1 & DW2
		if (TinselV2) {
			StopSample(pp[0]);
			return -1;
		} else {
			StopSample();
			return 0;
		}

	case STOPWALK:
		// Common to both DW1 & DW2 only
		StopWalk(pp[0]);
		return -1;

	case SUBTITLES:
		// Common to both DW1 & DW2
		Subtitles(pp[0]);
		return -1;

	case SWALK:
		// Common to both DW1 & DW2
		pp -= 5;			// 6 parameters
		Swalk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], -1, pic->escOn, pic->myEscape);
		return -6;

	case SWALKZ:
		// DW2 only
		pp -= 6;			// 7 parameters
		Swalk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6], pic->escOn, pic->myEscape);
		return -7;

	case SYSTEMVAR:
		// DW2 only
		pp[0] = SystemVar(pp[0]);
		return 0;

	case TAGACTOR:
		pp -= 2;			// 3 parameters
		TagActor(pp[0], pp[1], pp[2]);
		return -3;

	case TAGTAGXPOS:
	case TAGTAGYPOS:
	case TAGWALKXPOS:
	case TAGWALKYPOS:
		// DW2 only
		pp[0] = TagPos((MASTER_LIB_CODES)libCode, pp[0], pic->hPoly);
		return 0;

	case TALK:
		// Common to both DW1 & DW2
		pp -= 1;			// 2 parameters

		if (TinselV2)
			TalkOrSay(coroParam, IS_TALK, pp[1], 0, 0, pp[0], 0, false, pic->escOn, pic->myEscape);
		else if (pic->event == WALKIN || pic->event == WALKOUT)
			TalkOrSay(coroParam, IS_TALK, pp[1], 0, 0, pp[0], 0, false, pic->escOn, pic->myEscape);
		else
			TalkOrSay(coroParam, IS_TALK, pp[1], 0, 0, pp[0], pic->idActor, false, pic->escOn, pic->myEscape);
		return -2;

	case TALKAT:
		// Common to both DW1 & DW2
		if (TinselV2) {
			pp -= 4;			// 5 parameters
			TalkOrSay(coroParam, IS_TALKAT, pp[3], pp[1], pp[2], 0, pp[0], pp[4], pic->escOn, pic->myEscape);
			return -5;
		} else {
			pp -= 3;			// 4 parameters
			TalkAt(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myEscape);
			return -4;
		}

	case TALKATS:
		// DW1 only
		pp -= 4;			// 5 parameters
		TalkAtS(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pic->escOn, pic->myEscape);
		return -5;

	case TALKATTR:
		// DW1 only
		pp -= 2;			// 3 parameters
		TalkAttr(pp[0], pp[1], pp[2], pic->escOn, pic->myEscape);
		return -3;

	case TALKPALETTEINDEX:
		// DW1 only
		TalkPaletteIndex(pp[0]);
		return -1;

	case TALKRGB:
		// DW2 only
		TalkRGB(pp[0], pic->myEscape);
		return -3;

	case TALKVIA:
		// DW2 only
		TalkVia(pp[0]);
		return -1;

	case TEMPTAGFONT:
		// DW2 only
		TempTagFont(pp[0]);
		return -1;

	case TEMPTALKFONT:
		// DW2 only
		TempTalkFont(pp[0]);
		return -1;

	case THISOBJECT:
		// DW2 only
		pp[0] = ThisObject(pic->pinvo);
		return 0;

	case THISTAG:
		// DW2 only
		pp[0] = ThisTag(pic->hPoly);
		return 0;

	case TIMER:
		// Common to both DW1 & DW2
		pp[0] = TimerFn(pp[0]);
		return 0;

	case TOPIC:
		// DW2 only
		pp[0] = Topic();
		return 0;

	case TOPPLAY:
		// Common to both DW1 & DW2
		if (TinselV2) {
			pp -= 3;			// 4 parameters
			TopPlay(coroParam, pp[0], pp[1], pp[2], pp[3], pic->myEscape, pic->event);
			return -4;
		} else {
			pp -= 5;			// 6 parameters
			TopPlay(coroParam, pp[0], pp[1], pp[2], pp[5], pic->idActor, false, 0, pic->escOn, pic->myEscape);
			return -6;
		}

	case TOPWINDOW:
		// Common to both DW1 & DW2
		TopWindow(pp[0]);
		return -1;

	case TRANSLUCENTINDEX:
		// DW2 only
		TranslucentIndex(pp[0]);
		return -1;

	case TRYPLAYSAMPLE:
		// DW1 only
		pp -= 1;			// 2 parameters
		TryPlaySample(coroParam, pp[0], pp[1], pic->escOn, pic->myEscape);
		return -2;

	case UNDIMMUSIC:
		// DW2 only
		UnDimMusic();
		return 0;

	case UNHOOKSCENE:
		// Common to both DW1 & DW2
		UnHookSceneFn();
		return 0;

	case UNTAGACTOR:
		// DW1 only
		UnTagActorFn(pp[0]);
		return -1;

	case VIBRATE:
		// DW1 only
		Vibrate();
		return 0;

	case WAITFRAME:
		// Common to both DW1 & DW2
		pp -= 1;			// 2 parameters
		WaitFrame(coroParam, pp[0], pp[1], pic->escOn, pic->myEscape);
		return -2;

	case WAITKEY:
		// Common to both DW1 & DW2
		WaitKey(coroParam, pic->escOn, pic->myEscape);
		return 0;

	case WAITSCROLL:
		// DW2 only
		WaitScroll(coroParam, pic->myEscape);
		return 0;

	case WAITTIME:
		// Common to both DW1 & DW2
		pp -= 1;			// 2 parameters
		WaitTime(coroParam, pp[0], pp[1], pic->escOn, pic->myEscape);
		if (!coroParam && (pic->hCode == 0x3007540) && (pic->resumeState == RES_2))
			// FIXME: This is a hack to return control to the user after using the prunes in
			// the DW1 Demo, since I can't figure out how it's legitimately done
			Control(CONTROL_ON);

		return -2;

	case WALK:
		// Common to both DW1 & DW2
		pp -= 4;			// 5 parameters
		Walk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], false, -1, pic->escOn, pic->myEscape);
		return -5;

	case WALKED: {
		// Common to both DW1 & DW2
		pp -= 3;			// 4 parameters
		bool tmp = false;
		Walked(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myEscape, tmp);
		if (!coroParam) {
			// Only write the result to the stack if walked actually completed running.
			pp[0] = tmp;
		}
		}
		return -3;

	case WALKINGACTOR:
		// Common to both DW1 & DW2
		if (TinselV2) {
			// DW2 doesn't use a second parameter to WalkingActor
			WalkingActor(pp[0]);
			return -1;
		} else {
			pp -= 40;			// 41 parameters
			WalkingActor(pp[0], (SCNHANDLE *)&pp[1]);
			return -41;
		}

	case WALKPOLY:
		// Common to both DW1 & DW2
		if (TinselV2) {
			pp -= 1;			// 2 parameters
			WalkPoly(coroParam, pp[0], pp[1], pic->hPoly, pic->escOn, pic->myEscape);
			return -2;
		} else {
			pp -= 2;			// 3 parameters
			WalkPoly(coroParam, pp[0], pp[1], pic->hPoly, pic->escOn, pic->myEscape);
			return -3;
		}

	case WALKTAG:
		// Common to both DW1 & DW2
		if (TinselV2) {
			pp -= 1;			// 2 parameters
			WalkTag(coroParam, pp[0], pp[1], pic->hPoly, pic->escOn, pic->myEscape);
			return -2;
		} else {
			pp -= 2;			// 3 parameters
			WalkTag(coroParam, pp[0], pp[1], pic->hPoly, pic->escOn, pic->myEscape);
			return -3;
		}

	case WALKXPOS:
		// DW2 only
		pp[0] = WalkXPos();
		return 0;

	case WALKYPOS:
		// DW2 only
		pp[0] = WalkYPos();
		return 0;

	case WHICHCD:
		// DW2 only
		pp[0] = WhichCd();
		return 0;

	case WHICHINVENTORY:
		// Common to both DW1 & DW2
		pp[0] = WhichInventory();
		return 0;

	case ZZZZZZ:
		// DW2 only - dummy routine used during debugging
		return -1;

	default:
		error("Unsupported library function");
	}

	//error("Can't possibly get here");
}

} // End of namespace Tinsel
