/* 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.
 *
 * Handles the inventory and conversation windows.
 *
 * And the save/load game windows. Some of this will be platform
 * specific - I'll try to separate this ASAP.
 *
 * And there's still a bit of tidying and commenting to do yet.
 */

#include "tinsel/actors.h"
#include "tinsel/anim.h"
#include "tinsel/background.h"
#include "tinsel/config.h"
#include "tinsel/cursor.h"
#include "tinsel/drives.h"
#include "tinsel/dw.h"
#include "tinsel/film.h"
#include "tinsel/font.h"
#include "tinsel/graphics.h"
#include "tinsel/handle.h"
#include "tinsel/heapmem.h"
#include "tinsel/dialogs.h"
#include "tinsel/multiobj.h"
#include "tinsel/music.h"
#include "tinsel/palette.h"
#include "tinsel/pcode.h"
#include "tinsel/pdisplay.h"
#include "tinsel/pid.h"
#include "tinsel/polygons.h"
#include "tinsel/savescn.h"
#include "tinsel/sched.h"
#include "tinsel/scn.h"
#include "common/serializer.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"		// For engine access
#include "tinsel/token.h"

#include "common/textconsole.h"

namespace Tinsel {

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

#define HOPPER_FILENAME		"hopper"

#define INV_PICKUP	PLR_SLEFT		// Local names
#define INV_LOOK	PLR_SRIGHT		//	for button events
#define INV_ACTION	PLR_DLEFT		//
// For SlideSlider() and similar
enum SSFN {
	S_START, S_SLIDE, S_END, S_TIMEUP, S_TIMEDN
};

/** attribute values - may become bit field if further attributes are added */
enum {
	IO_ONLYINV1	= 0x01,
	IO_ONLYINV2	= 0x02,
	IO_DROPCODE	= 0x04
};

//-----------------------
// Moveable window translucent rectangle position limits
enum {
	MAXLEFT		= 315,		//
	MINRIGHT	= 3,		// These values keep 2 pixcells
	MINTOP		= -13,		// of header on the screen.
	MAXTOP		= 195		//
};

//-----------------------
// Indices into hWinParts's reels

enum PARTS_INDEX {
	IX_SLIDE = 0,		// Slider
	IX_V26 = 1,
	IX_V52 = 2,
	IX_V78 = 3,
	IX_V104 = 4,
	IX_V130 = 5,
	IX_H26 = 6,
	IX_H52 = 7,
	IX_H78 = 8,
	IX_H104 = 9,
	IX_H130 = 10,
	IX_H156 = 11,
	IX_H182 = 12,
	IX_H208 = 13,
	IX_H234 = 14,
	IX_TL = 15,		// Top left corner
	IX_TR = 16,		// Top right corner
	IX_BL = 17,		// Bottom left corner
	IX_BR = 18,		// Bottom right corner

	IX1_H25 = 19,
	IX1_V11 = 20,
	IX1_RTL = 21,		// Re-sizing top left corner
	IX1_RTR = 22,		// Re-sizing top right corner
	IX1_RBR = 23,		// Re-sizing bottom right corner
	IX1_CURLR = 24,		// }
	IX1_CURUD = 25,		// }
	IX1_CURDU = 26,		// } Custom cursors
	IX1_CURDD = 27,		// }
	IX1_CURUP = 28,		// }
	IX1_CURDOWN = 29,	// }
	IX1_MDGROOVE = 30,	// 'Mixing desk' slider background
	IX1_MDSLIDER= 34,	// 'Mixing desk' slider
	IX1_BLANK1 = 35,		//
	IX1_BLANK2 = 36,		//
	IX1_BLANK3 = 37,		//
	IX1_CIRCLE1 = 38,	//
	IX1_CIRCLE2 = 39,	//
	IX1_CROSS1 = 40,		//
	IX1_CROSS2 = 41,		//
	IX1_CROSS3 = 42,		//
	IX1_QUIT1 = 43,	//
	IX1_QUIT2 = 44,	//
	IX1_QUIT3 = 45,	//
	IX1_TICK1 = 46,		//
	IX1_TICK2 = 47,		//
	IX1_TICK3 = 48,		//
	IX1_NTR = 49,		// New top right corner

	IX2_RTL = 19,			// Re-sizing top left corner
	IX2_RTR = 20,			// Re-sizing top right corner
	IX2_RBR = 21,			// Re-sizing bottom right corner
	IX2_CURLR = 22,		// }
	IX2_CURUD = 23,		// }
	IX2_CURDU = 24,		// } Custom cursors
	IX2_CURDD = 25,		// }
	IX2_MDGROOVE = 26,	// 'Mixing desk' slider background
	IX2_MDSLIDER = 27,	// 'Mixing desk' slider
	IX2_CIRCLE1 = 28,	//
	IX2_CIRCLE2 = 29,	//
	IX2_CROSS1 = 30,	//
	IX2_CROSS2 = 31,	//
	IX2_CROSS3 = 32,	//
	IX2_TICK1 = 33,		//
	IX2_TICK2 = 34,		//
	IX2_TICK3 = 35,		//
	IX2_NTR = 36,		// New top right corner
	IX2_TR4 = 37,
	IX2_LEFT1 = 38,
	IX2_LEFT2 = 39,
	IX2_RIGHT1 = 40,
	IX2_RIGHT2 = 41,

	T1_HOPEDFORREELS = 50,
	T2_HOPEDFORREELS = 42
};

// The following defines select the correct constant depending on Tinsel version
#define IX_CROSS1	(TinselV2 ? IX2_CROSS1 :	IX1_CROSS1)
#define IX_CURDD	(TinselV2 ? IX2_CURDD :		IX1_CURDD)
#define IX_CURDU	(TinselV2 ? IX2_CURDU :		IX1_CURDU)
#define IX_CURLR	(TinselV2 ? IX2_CURLR :		IX1_CURLR)
#define IX_CURUD	(TinselV2 ? IX2_CURUD :		IX1_CURUD)
#define IX_CURUL	(TinselV2 ? IX2_CURUL :		IX1_CURUL)
#define IX_MDGROOVE	(TinselV2 ? IX2_MDGROOVE :	IX1_MDGROOVE)
#define IX_MDSLIDER	(TinselV2 ? IX2_MDSLIDER :	IX1_MDSLIDER)
#define IX_NTR		(TinselV2 ? IX2_NTR :		IX1_NTR)
#define IX_RBR		(TinselV2 ? IX2_RBR :		IX1_RBR)
#define IX_RTL		(TinselV2 ? IX2_RTL :		IX1_RTL)
#define IX_RTR		(TinselV2 ? IX2_RTR :		IX1_RTR)
#define IX_TICK1	(TinselV2 ? IX2_TICK1 :		IX1_TICK1)



#define NORMGRAPH	0
#define DOWNGRAPH	1
#define HIGRAPH		2
//-----------------------
#define FIX_UK		0
#define FIX_FR		1
#define FIX_GR		2
#define FIX_IT		3
#define FIX_SP		4
#define FIX_USA		5
#define HOPEDFORFREELS	6	// Expected flag reels
//-----------------------

#define MAX_ININV	(TinselV2 ? 160 : 150)		// Max in an inventory
#define MAX_ININV_TOT	160
#define MAX_PERMICONS	10	// Max permanent conversation icons

#define MAXHICONS	10	// Max dimensions of
#define MAXVICONS	6	// an inventory window

#define ITEM_WIDTH	(TinselV2 ? 50 : 25)	// Dimensions of an icon
#define ITEM_HEIGHT	(TinselV2 ? 50 : 25)	//
#define I_SEPARATION	(TinselV2 ? 2 : 1)	// Item separation

#define NM_TOFF		11	// Title text Y offset from top
#define NM_TBT		(TinselV2 ? 4 : 0)		// Y, title box top
#define NM_TBB		33
#define NM_LSX		(TinselV2 ? 4 : 0)		// X, left side
#define NM_BSY		(TinselV2 ? -9 : - M_TH + 1)
#define NM_RSX		(TinselV2 ? -9 : - M_SW + 1)
#define NM_SBL		(-27)
#define NM_SLH		(TinselV2 ? 11 : 5)	// Slider height
#define NM_SLX			(-11)	// Slider X offset (from right)

#define NM_BG_POS_X (TinselV2 ? 9 : 1)		// }
#define NM_BG_POS_Y (TinselV2 ? 9 : 1)		// } Offset of translucent rectangle
#define NM_BG_SIZ_X (TinselV2 ? -18 : -3)	// }
#define NM_BG_SIZ_Y (TinselV2 ? -18 : -3)	// } How much larger it is than edges

#define NM_RS_T_INSET		3
#define NM_RS_B_INSET		4
#define NM_RS_L_INSET		3
#define NM_RS_R_INSET		4
#define NM_RS_THICKNESS		5
#define NM_MOVE_AREA_B_Y	30
#define NM_SLIDE_INSET		(TinselV2 ? 18 : 9)	// X offset (from right) of left of scroll region
#define NM_SLIDE_THICKNESS	(TinselV2 ? 13 : 7)		// thickness of scroll region
#define NM_UP_ARROW_TOP		34	// Y offset of top of up arrow
#define NM_UP_ARROW_BOTTOM	49	// Y offset of bottom of up arrow
#define NM_DN_ARROW_TOP		22	// Y offset (from bottom) of top of down arrow
#define NM_DN_ARROW_BOTTOM	5	// Y offset (from bottom) of bottom of down arrow

#define MD_YBUTTOP	(TinselV2 ? 2 : 9)
#define MD_YBUTBOT	(TinselV2 ? 16 : 0)
#define MD_XLBUTL	(TinselV2 ? 4 : 1)
#define MD_XLBUTR	(TinselV2 ? 26 : 10)
#define MD_XRBUTL	(TinselV2 ? 173 : 105)
#define MD_XRBUTR	(TinselV2 ? 195 : 114)
#define ROTX1 60	// Rotate button's offsets from the center

// Number of objects that makes up an empty window
#define MAX_WCOMP	21		// 4 corners + (3+3) sides + (2+2) extra sides
					// + Bground + title + slider
					// + more Needed for save game window

#define MAX_ICONS	MAXHICONS*MAXVICONS

#define MAX_NAME_RIGHT (TinselV2 ? 417 : 213)

#define SLIDE_RANGE	(TinselV2 ? 120 : 81)
#define SLIDE_MINX	(TinselV2 ? 25 : 8)
#define SLIDE_MAXX	(TinselV2 ? 25 + 120 : 8 + 81)

#define MDTEXT_YOFF	(TinselV2 ? -1 : 6)
#define MDTEXT_XOFF	-4
#define TOG2_YOFF	-22
#define ROT_YOFF	48
#define TYOFF (TinselV2 ? 4 : 0)
#define FLAGX (-5)
#define FLAGY 4


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

//----- Permanent data (compiled in) -----

// Save game name editing cursor

#define CURSOR_CHAR	'_'
char sCursor[2]	= { CURSOR_CHAR, 0 };
static const int hFillers[MAXHICONS] = {
	IX_H26,			// 2 icons wide
	IX_H52,			// 3
	IX_H78,			// 4
	IX_H104,		// 5
	IX_H130,		// 6
	IX_H156,		// 7
	IX_H182,		// 8
	IX_H208,		// 9
	IX_H234			// 10 icons wide
};
static const int vFillers[MAXVICONS] = {
	IX_V26,			// 2 icons high
	IX_V52,			// 3
	IX_V78,			// 4
	IX_V104,		// 5
	IX_V130			// 6 icons high
};


//----- Permanent data (set once) -----

static SCNHANDLE g_hWinParts = 0;	// Window members and cursors' graphic data
static SCNHANDLE g_flagFilm = 0;	// Window members and cursors' graphic data
static SCNHANDLE g_configStrings[20];

static INV_OBJECT *g_invObjects = NULL;	// Inventory objects' data
static int g_numObjects = 0;				// Number of inventory objects
static SCNHANDLE *g_invFilms = NULL;
static bool g_bNoLanguage = false;
static DIRECTION g_initialDirection;

//----- Permanent data (updated, valid while inventory closed) -----

static enum {NO_INV, IDLE_INV, ACTIVE_INV, BOGUS_INV} g_InventoryState;

static int g_heldItem = INV_NOICON;	// Current held item

static SCNHANDLE g_heldFilm;

struct INV_DEF {

	int MinHicons;		// }
	int MinVicons;		// } Dimension limits
	int MaxHicons;		// }
	int MaxVicons;		// }

	int NoofHicons;		// }
	int NoofVicons;		// } Current dimentsions

	int contents[160];	// Contained items
	int NoofItems;			// Current number of held items

	int FirstDisp;		// Index to first item currently displayed

	int inventoryX;		// } Display position
	int inventoryY;		// }
	int otherX;		// } Display position
	int otherY;		// }

	int MaxInvObj;		// Max. allowed contents

	SCNHANDLE hInvTitle;	// Window heading

	bool resizable;		// Re-sizable window?
	bool bMoveable;		// Moveable window?

	int sNoofHicons;	// }
	int sNoofVicons;	// } Current dimensions

	bool bMax;		// Maximised last time open?

};

static INV_DEF g_InvD[NUM_INV];		// Conversation + 2 inventories + ...


// Permanent contents of conversation inventory
static int g_permIcons[MAX_PERMICONS];	// Basic items i.e. permanent contents
static int g_numPermIcons = 0;			// - copy to conv. inventory at pop-up time
static int g_numEndIcons = 0;

//----- Data pertinant to current active inventory -----

static int g_ino = 0;		// Which inventory is currently active

static bool g_InventoryHidden = false;
static bool g_InventoryMaximised = false;

static enum {	ID_NONE, ID_MOVE, ID_SLIDE,
		ID_BOTTOM, ID_TOP, ID_LEFT, ID_RIGHT,
		ID_TLEFT, ID_TRIGHT, ID_BLEFT, ID_BRIGHT,
		ID_CSLIDE, ID_MDCONT } g_InvDragging;

static int g_SuppH = 0;		// 'Linear' element of
static int g_SuppV = 0;		// dimensions during re-sizing

static int g_Ychange = 0;		//
static int g_Ycompensate = 0;		// All to do with re-sizing.
static int g_Xchange = 0;		//
static int g_Xcompensate = 0;		//

static bool g_ItemsChanged = 0;	// When set, causes items to be re-drawn

static bool g_bReOpenMenu = 0;

static int g_TL = 0, g_TR = 0, g_BL = 0, g_BR = 0;	// Used during window construction
static int g_TLwidth = 0, g_TLheight = 0;	//
static int g_TRwidth = 0;		//
static int g_BLheight = 0;		//

static LANGUAGE g_displayedLanguage;

static OBJECT	*g_objArray[MAX_WCOMP];	// Current display objects (window)
static OBJECT	*g_iconArray[MAX_ICONS];	// Current display objects (icons)
static ANIM		g_iconAnims[MAX_ICONS];
static OBJECT	*g_DobjArray[MAX_WCOMP];	// Current display objects (re-sizing window)

static OBJECT *g_RectObject = 0, *g_SlideObject = 0;	// Current display objects, for reference
					// objects are in objArray.

static int g_sliderYpos = 0;			// For positioning the slider
static int g_sliderYmax = 0, g_sliderYmin = 0;	//

#define sliderRange	(g_sliderYmax - g_sliderYmin)

// Also to do with the slider
static struct { int n; int y; } g_slideStuff[MAX_ININV_TOT+1];

#define MAXSLIDES 4
struct MDSLIDES {
	int	num;
	OBJECT	*obj;
	int	min, max;
};
static MDSLIDES g_mdSlides[MAXSLIDES];
static int g_numMdSlides = 0;

static int g_GlitterIndex = 0;

// Icon clicked on to cause an event
// - Passed to conversation polygon or actor code via Topic()
// - (sometimes) Passed to inventory icon code via OtherObject()
static int g_thisIcon = 0;

static CONV_PARAM g_thisConvFn;				// Top, 'Middle' or Bottom
static HPOLYGON g_thisConvPoly = 0;			// Conversation code is in a polygon code block
static int g_thisConvActor;					// ...or an actor's code block.
static int g_pointedIcon = INV_NOICON;		// used by InvLabels - icon pointed to on last call
static volatile int g_PointedWaitCount = 0;	// used by ObjectProcess - fix the 'repeated pressing bug'
static int g_sX = 0;							// used by SlideMSlider() - current x-coordinate
static int g_lX = 0;							// used by SlideMSlider() - last x-coordinate

static bool g_bMoveOnUnHide;	// Set before start of conversation
				// - causes conversation to be started in a sensible place

//----- Data pertinant to configure (incl. load/save game) -----

#define COL_MAINBOX	TBLUE1		// Base blue color
#define COL_BOX		TBLUE1
#define COL_HILIGHT	TBLUE4

#ifdef JAPAN
#define BOX_HEIGHT	17
#define EDIT_BOX1_WIDTH	149
#else
#define BOX_HEIGHT	13
#define EDIT_BOX1_WIDTH	145
#endif
#define EDIT_BOX2_WIDTH	166

#define T2_EDIT_BOX1_WIDTH 290
#define T2_EDIT_BOX2_WIDTH 322
#define T2_BOX_HEIGHT 26

//----- Data pertinant to scene hoppers ------------------------

#include "common/pack-start.h"	// START STRUCT PACKING

struct HOPPER {
	uint32		hScene;
	SCNHANDLE	hSceneDesc;
	uint32		numEntries;
	uint32		entryIndex;
} PACKED_STRUCT;
typedef HOPPER *PHOPPER;

struct HOPENTRY {
	uint32	eNumber;	// entrance number
	SCNHANDLE hDesc;	// handle to entrance description
	uint32	flags;
} PACKED_STRUCT;
typedef HOPENTRY *PHOPENTRY;

#include "common/pack-end.h"	// END STRUCT PACKING

static PHOPPER		g_pHopper;
static PHOPENTRY	g_pEntries;
static int g_numScenes;

static int g_numEntries;

static PHOPPER g_pChosenScene = NULL;

static int g_lastChosenScene;
static bool g_bRemember;

//--------------------------------------------------------------



enum BTYPE {
	RGROUP,		///< Radio button group - 1 is selectable at a time. Action on double click
	ARSBUT,		///< Action if a radio button is selected
	AABUT,		///< Action always
	AATBUT,		///< Action always, text box
	ARSGBUT,
	AAGBUT,		///< Action always, graphic button
	SLIDER,		///< Not a button at all
	TOGGLE,		///< Discworld 1 toggle
	TOGGLE1,	///< Discworld 2 toggle type 1
	TOGGLE2,	///< Discworld 2 toggle type 2
	DCTEST,
	FLIP,
	FRGROUP,
	ROTATE,
	NOTHING
};

enum BFUNC {
	NOFUNC,
	SAVEGAME,
	LOADGAME,
	IQUITGAME,
	CLOSEWIN,
	OPENLOAD,
	OPENSAVE,
	OPENREST,
	OPENSOUND,
	OPENCONT,
#ifndef JAPAN
	OPENSUBT,
#endif
	OPENQUIT,
	INITGAME,
	MUSICVOL,

	HOPPER2,		// Call up Scene Hopper 2
	BF_CHANGESCENE,

	CLANG,
	RLANG
#ifdef MAC_OPTIONS
	, MASTERVOL, SAMPVOL
#endif
};

#define NO_HEADING		((SCNHANDLE)-1)
#define USE_POINTER		(-1)
#define SIX_LOAD_OPTION		0
#define SIX_SAVE_OPTION		1
#define SIX_RESTART_OPTION	2
#define SIX_SOUND_OPTION	3
#define SIX_CONTROL_OPTION	4
#ifndef JAPAN
#define SIX_SUBTITLES_OPTION	5
#endif
#define SIX_QUIT_OPTION		6
#define SIX_RESUME_OPTION	7
#define SIX_LOAD_HEADING	8
#define SIX_SAVE_HEADING	9
#define SIX_RESTART_HEADING	10
#define SIX_MVOL_SLIDER		11
#define SIX_SVOL_SLIDER		12
#define SIX_VVOL_SLIDER		13
#define SIX_DCLICK_SLIDER	14
#define SIX_DCLICK_TEST		15
#define SIX_SWAP_TOGGLE		16
#define SIX_TSPEED_SLIDER	17
#define SIX_STITLE_TOGGLE	18
#define SIX_QUIT_HEADING	19

enum TM {TM_POINTER, TM_INDEX, TM_STRINGNUM, TM_NONE};

struct CONFBOX {
	BTYPE	boxType;
	BFUNC	boxFunc;
	TM		textMethod;

	char	*boxText;
	int	ixText;
	int	xpos;
	int	ypos;
	int	w;		// Doubles as max value for SLIDERs
	int	h;		// Doubles as iteration size for SLIDERs
	int	*ival;
	int	bi;		// Base index for AAGBUTs
};

struct CONFINIT {
	int	h;
	int	v;
	int	x;
	int	y;
	bool bExtraWin;
	CONFBOX *Box;
	int	NumBoxes;
	uint32	ixHeading;
};

#define BW	44	// Width of crosses and ticks etc. buttons
#define BH	41	// Height of crosses and ticks etc. buttons

/*-------------------------------------------------------------*\
| This is the main menu (that comes up when you hit F1 on a PC)	|
\*-------------------------------------------------------------*/

#ifdef JAPAN
#define FBY	11	// y-offset of first button
#define FBX	13	// x-offset of first button
#else
#define FBY	20	// y-offset of first button
#define FBX	15	// x-offset of first button
#endif

#define OPTX	33
#define OPTY	30
#define BOX_V_SEP	7

#define BOXX	56	// X-position of text boxes
#define BOXY	50	// Y-position of text boxes
#define T2_OPTX	33
#define T2_OPTY	36
#define T2_BOX_V_SEP	12
#define T2_BOX_V2_SEP	6

static CONFBOX t1OptionBox[] = {

 { AATBUT, OPENLOAD, TM_NONE, NULL, SIX_LOAD_OPTION,	FBX, FBY,			EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
 { AATBUT, OPENSAVE, TM_NONE, NULL, SIX_SAVE_OPTION,	FBX, FBY + (BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
 { AATBUT, OPENREST, TM_NONE, NULL, SIX_RESTART_OPTION,	FBX, FBY + 2*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
 { AATBUT, OPENSOUND, TM_NONE, NULL, SIX_SOUND_OPTION,	FBX, FBY + 3*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
 { AATBUT, OPENCONT, TM_NONE, NULL, SIX_CONTROL_OPTION,	FBX, FBY + 4*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
#ifdef JAPAN
// TODO: If in JAPAN mode, simply disable the subtitles button?
 { AATBUT, OPENQUIT, NULL, SIX_QUIT_OPTION,	FBX, FBY + 5*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
 { AATBUT, CLOSEWIN, NULL, SIX_RESUME_OPTION,	FBX, FBY + 6*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }
#else
 { AATBUT, OPENSUBT, TM_NONE, NULL, SIX_SUBTITLES_OPTION,FBX, FBY + 5*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
 { AATBUT, OPENQUIT, TM_NONE, NULL, SIX_QUIT_OPTION,	FBX, FBY + 6*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
 { AATBUT, CLOSEWIN, TM_NONE, NULL, SIX_RESUME_OPTION,	FBX, FBY + 7*(BOX_HEIGHT + 2),	EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }
#endif

};

static CONFINIT t1ciOption	= { 6, 5, 72, 23, false, t1OptionBox,	ARRAYSIZE(t1OptionBox),	NO_HEADING };

static CONFBOX t2OptionBox[] = {

 { AATBUT, OPENLOAD, TM_INDEX, NULL, SS_LOAD_OPTION,	T2_OPTX, T2_OPTY,									T2_EDIT_BOX1_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
 { AATBUT, OPENSAVE, TM_INDEX, NULL, SS_SAVE_OPTION,	T2_OPTX, T2_OPTY + (T2_BOX_HEIGHT + T2_BOX_V_SEP),	T2_EDIT_BOX1_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
 { AATBUT, OPENREST, TM_INDEX, NULL, SS_RESTART_OPTION,	T2_OPTX, T2_OPTY + 2*(T2_BOX_HEIGHT + T2_BOX_V_SEP),	T2_EDIT_BOX1_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
 { AATBUT, OPENSOUND, TM_INDEX, NULL, SS_SOUND_OPTION,	T2_OPTX, T2_OPTY + 3*(T2_BOX_HEIGHT + T2_BOX_V_SEP),	T2_EDIT_BOX1_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
 { AATBUT, OPENQUIT, TM_INDEX, NULL, SS_QUIT_OPTION,	T2_OPTX, T2_OPTY + 4*(T2_BOX_HEIGHT + T2_BOX_V_SEP),	T2_EDIT_BOX1_WIDTH, T2_BOX_HEIGHT, NULL, 0 }

};

static CONFINIT t2ciOption = { 6, 4, 144, 60, false, t2OptionBox, sizeof(t2OptionBox)/sizeof(CONFBOX), NO_HEADING };

#define ciOption (TinselV2 ? t2ciOption : t1ciOption)
#define optionBox (TinselV2 ? t2OptionBox : t1OptionBox)

/*-------------------------------------------------------------*\
| These are the load and save game menus.			|
\*-------------------------------------------------------------*/

#define NUM_RGROUP_BOXES	9

#ifdef JAPAN
#define NUM_RGROUP_BOXES	7	// number of visible slots
#define SY		32	// y-position of first slot
#else
#define NUM_RGROUP_BOXES	9	// number of visible slots
#define SY		31	// y-position of first slot
#endif

static CONFBOX t1LoadBox[NUM_RGROUP_BOXES+2] = {
	{ RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY,				EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
	{ RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
	{ RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
	{ RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
	{ RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
	{ RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
	{ RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
#ifndef JAPAN
	{ RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
	{ RGROUP, LOADGAME, TM_NONE, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
#endif
	{ ARSGBUT, LOADGAME, TM_NONE, NULL, USE_POINTER, 230, 44,	23, 19, NULL, IX1_TICK1 },
	{ AAGBUT, CLOSEWIN, TM_NONE, NULL, USE_POINTER, 230, 44+47,	23, 19, NULL, IX1_CROSS1 }
};

static CONFBOX t2LoadBox[] = {
	{ RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY,				T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + (T2_BOX_HEIGHT + T2_BOX_V2_SEP),	T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 2*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 3*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 4*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 5*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 6*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 7*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, LOADGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 8*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },

	{ ARSGBUT, LOADGAME, TM_NONE, NULL, 0, 460, 100,	BW, BH, NULL, IX2_TICK1 },
	{ AAGBUT, CLOSEWIN,  TM_NONE, NULL, 0, 460, 100+100,	BW, BH, NULL, IX2_CROSS1 }
};

static CONFINIT t1ciLoad	= { 10, 6, 20, 16, true, t1LoadBox,	ARRAYSIZE(t1LoadBox), SIX_LOAD_HEADING };
static CONFINIT t2ciLoad	= { 10, 6, 40, 16, true, t2LoadBox, sizeof(t2LoadBox)/sizeof(CONFBOX), SS_LOAD_HEADING };


static CONFBOX t1SaveBox[NUM_RGROUP_BOXES+2] = {
	{ RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28,	SY,			EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
	{ RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28,	SY + (BOX_HEIGHT + 2),	EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
	{ RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28,	SY + 2*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
	{ RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28,	SY + 3*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
	{ RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28,	SY + 4*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
	{ RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28,	SY + 5*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
	{ RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28,	SY + 6*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
#ifndef JAPAN
	{ RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28,	SY + 7*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
	{ RGROUP, SAVEGAME, TM_NONE, NULL, USE_POINTER, 28,	SY + 8*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
#endif
	{ ARSGBUT, SAVEGAME, TM_NONE, NULL,USE_POINTER, 230, 44,	23, 19, NULL, IX1_TICK1 },
	{ AAGBUT, CLOSEWIN, TM_NONE, NULL, USE_POINTER, 230, 44+47,	23, 19, NULL, IX1_CROSS1 }
};

static CONFBOX t2SaveBox[] = {
 { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY,				T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
 { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + (T2_BOX_HEIGHT + T2_BOX_V2_SEP),	T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
 { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 2*(T2_BOX_HEIGHT + T2_BOX_V2_SEP),	T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
 { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 3*(T2_BOX_HEIGHT + T2_BOX_V2_SEP),	T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
 { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 4*(T2_BOX_HEIGHT + T2_BOX_V2_SEP),	T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
 { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 5*(T2_BOX_HEIGHT + T2_BOX_V2_SEP),	T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
 { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 6*(T2_BOX_HEIGHT + T2_BOX_V2_SEP),	T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
 { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 7*(T2_BOX_HEIGHT + T2_BOX_V2_SEP),	T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
 { RGROUP, SAVEGAME, TM_POINTER, NULL, 0, BOXX, BOXY + 8*(T2_BOX_HEIGHT + T2_BOX_V2_SEP),	T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },

 { ARSGBUT, SAVEGAME, TM_NONE, NULL, 0, 460, 100,	BW, BH, NULL, IX2_TICK1 },
 { AAGBUT, CLOSEWIN,  TM_NONE, NULL, 0, 460, 100+100,	BW, BH, NULL, IX2_CROSS1 }
};

static CONFINIT t1ciSave	= { 10, 6, 20, 16, true, t1SaveBox,	ARRAYSIZE(t1SaveBox),	SIX_SAVE_HEADING };
static CONFINIT t2ciSave	= { 10, 6, 40, 16, true, t2SaveBox, sizeof(t2SaveBox)/sizeof(CONFBOX), SS_SAVE_HEADING };

#define ciLoad (TinselV2 ? t2ciLoad : t1ciLoad)
#define loadBox (TinselV2 ? t2LoadBox : t1LoadBox)
#define ciSave (TinselV2 ? t2ciSave : t1ciSave)
#define saveBox (TinselV2 ? t2SaveBox : t1SaveBox)

/*-------------------------------------------------------------*\
| This is the restart confirmation 'menu'.			|
\*-------------------------------------------------------------*/

static CONFBOX t1RestartBox[] = {
#ifdef JAPAN
	{ AAGBUT, INITGAME, TM_NONE, NULL, USE_POINTER, 96, 44,	23, 19, NULL, IX_TICK1 },
	{ AAGBUT, CLOSEWIN, TM_NONE, NULL, USE_POINTER, 56, 44,	23, 19, NULL, IX_CROSS1 }
#else
	{ AAGBUT, INITGAME, TM_NONE, NULL, USE_POINTER, 70, 28,	23, 19, NULL, IX1_TICK1 },
	{ AAGBUT, CLOSEWIN, TM_NONE, NULL, USE_POINTER, 30, 28,	23, 19, NULL, IX1_CROSS1 }
#endif
};

static CONFBOX t1RestartBoxPSX[] = {
	{ AAGBUT, INITGAME, TM_NONE, NULL, USE_POINTER, 122, 48,	23, 19, NULL, IX1_TICK1 },
	{ AAGBUT, CLOSEWIN, TM_NONE, NULL, USE_POINTER, 82, 48,	23, 19, NULL, IX1_CROSS1 }
};

static CONFBOX t2RestartBox[] = {
	{ AAGBUT, INITGAME, TM_NONE, NULL, 0, 140, 78, BW, BH, NULL, IX2_TICK1 },
	{ AAGBUT, CLOSEWIN, TM_NONE, NULL, 0, 60, 78,  BW, BH, NULL, IX2_CROSS1 }
};

#ifdef JAPAN
static CONFINIT t1ciRestart	= { 6, 2, 72, 53, false, t1RestartBox,	ARRAYSIZE(t1RestartBox),	SIX_RESTART_HEADING };
#else
static CONFINIT t1ciRestart	= { 4, 2, 98, 53, false, t1RestartBox,	ARRAYSIZE(t1RestartBox),	SIX_RESTART_HEADING };
#endif
static CONFINIT t1ciRestartPSX	= { 8, 2, 46, 53, false, t1RestartBoxPSX,	ARRAYSIZE(t1RestartBoxPSX),	SIX_RESTART_HEADING };
static CONFINIT t2ciRestart	= { 4, 2, 196, 53, false, t2RestartBox, sizeof(t2RestartBox)/sizeof(CONFBOX), SS_RESTART_HEADING };

#define ciRestart (TinselV2 ? t2ciRestart : (TinselV1PSX ? t1ciRestartPSX : t1ciRestart))

/*-------------------------------------------------------------*\
| This is the sound control 'menu'. In Discworld 2, it also		|
| contains the subtitles and language selection.				|
\*-------------------------------------------------------------*/

static CONFBOX t1SoundBox[] = {
	{ SLIDER, MUSICVOL, TM_NONE, NULL, SIX_MVOL_SLIDER,	142, 25,	Audio::Mixer::kMaxChannelVolume, 2, 0 /*&_vm->_config->_musicVolume*/, 0 },
	{ SLIDER, NOFUNC, TM_NONE, NULL, SIX_SVOL_SLIDER,	142, 25+40,	Audio::Mixer::kMaxChannelVolume, 2, 0 /*&_vm->_config->_soundVolume*/, 0 },
	{ SLIDER, NOFUNC, TM_NONE, NULL, SIX_VVOL_SLIDER,	142, 25+2*40,	Audio::Mixer::kMaxChannelVolume, 2, 0 /*&_vm->_config->_voiceVolume*/, 0 }
};

static CONFBOX t2SoundBox[] = {
	{ SLIDER, MUSICVOL, TM_INDEX, NULL, SS_MVOL_SLIDER, 280, 50,      Audio::Mixer::kMaxChannelVolume, 2, 0 /*&_vm->_config->_musicVolume*/, 0 },
	{ SLIDER, NOFUNC, TM_INDEX, NULL, SS_SVOL_SLIDER,   280, 50+30,   Audio::Mixer::kMaxChannelVolume, 2, 0 /*&_vm->_config->_soundVolume*/, 0 },
	{ SLIDER, NOFUNC, TM_INDEX, NULL, SS_VVOL_SLIDER,   280, 50+2*30, Audio::Mixer::kMaxChannelVolume, 2, 0 /*&_vm->_config->_voiceVolume*/, 0 },

	{ SLIDER, NOFUNC, TM_INDEX, NULL, SS_TSPEED_SLIDER, 280, 160, 100, 2, 0 /*&_vm->_config->_textSpeed*/, 0 },
	{ TOGGLE2, NOFUNC, TM_INDEX, NULL, SS_STITLE_TOGGLE, 100, 220, BW, BH, 0 /*&_vm->_config->_useSubtitles*/, 0 },
	{ ROTATE, NOFUNC, TM_INDEX, NULL, SS_LANGUAGE_SELECT, 320,220, BW, BH, NULL, 0 }
};

static CONFINIT t1ciSound	= { 10, 5, 20, 16, false, t1SoundBox, ARRAYSIZE(t1SoundBox), NO_HEADING };
static CONFINIT t2ciSound = { 10, 5, 40, 16, false, t2SoundBox, sizeof(t2SoundBox)/sizeof(CONFBOX), SS_SOUND_HEADING };

#define ciSound (TinselV2 ? t2ciSound : t1ciSound)

/*-------------------------------------------------------------*\
| This is the (mouse) control 'menu'.				|
\*-------------------------------------------------------------*/

static int bFlipped;	// looks like this is just so the code has something to alter!

static CONFBOX controlBox[] = {
	{ SLIDER, NOFUNC, TM_NONE, NULL, SIX_DCLICK_SLIDER,	142, 25,	3*DOUBLE_CLICK_TIME, 1, 0 /*&_vm->_config->_dclickSpeed*/, 0 },
	{ FLIP, NOFUNC, TM_NONE, NULL, SIX_DCLICK_TEST,		142, 25+30,	23, 19, &bFlipped, IX1_CIRCLE1 },
#ifdef JAPAN
	{ TOGGLE, NOFUNC, TM_NONE, NULL, SIX_SWAP_TOGGLE,	205, 25+70,	23, 19, 0 /*&_vm->_config->_swapButtons*/, 0 }
#else
	{ TOGGLE, NOFUNC, TM_NONE, NULL, SIX_SWAP_TOGGLE,	155, 25+70,	23, 19, 0 /*&_vm->_config->_swapButtons*/, 0 }
#endif
};

static CONFINIT ciControl	= { 10, 5, 20, 16, false, controlBox,	ARRAYSIZE(controlBox),	NO_HEADING };

/*-------------------------------------------------------------*\
| This is the subtitles 'menu'.					|
\*-------------------------------------------------------------*/

static CONFBOX subtitlesBox[] = {
	{ SLIDER, NOFUNC, TM_NONE, NULL, SIX_TSPEED_SLIDER,	142, 20,	100, 2, 0 /*&_vm->_config->_textSpeed*/, 0 },
	{ TOGGLE, NOFUNC, TM_NONE, NULL, SIX_STITLE_TOGGLE,	142, 20+40,	23, 19, 0 /*&_vm->_config->_useSubtitles*/, 0 },
};

static CONFBOX subtitlesBox3Flags[] = {
	{ FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER,	15, 118,	56, 32, NULL, FIX_FR },
	{ FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER,	85, 118,	56, 32, NULL, FIX_GR },
	{ FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER,	155, 118,	56, 32, NULL, FIX_SP },

	{ SLIDER, NOFUNC, TM_NONE, NULL, SIX_TSPEED_SLIDER,	142, 20,	100, 2, 0 /*&_vm->_config->_textSpeed*/, 0 },
	{ TOGGLE, NOFUNC, TM_NONE, NULL, SIX_STITLE_TOGGLE,	142, 20+40,	23, 19, 0 /*&_vm->_config->_useSubtitles*/, 0 },

	{ ARSGBUT, CLANG, TM_NONE, NULL, USE_POINTER,	230, 110,	23, 19, NULL, IX1_TICK1 },
	{ AAGBUT, RLANG, TM_NONE, NULL, USE_POINTER,	230, 140,	23, 19, NULL, IX1_CROSS1 }
};

static CONFBOX subtitlesBox4Flags[] = {
	{ FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER,	20, 100,	56, 32, NULL, FIX_FR },
	{ FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER,	108, 100,	56, 32, NULL, FIX_GR },
	{ FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER,	64, 137,	56, 32, NULL, FIX_IT },
	{ FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER,	152, 137,	56, 32, NULL, FIX_SP },

	{ SLIDER, NOFUNC, TM_NONE, NULL, SIX_TSPEED_SLIDER,	142, 20,	100, 2, 0 /*&_vm->_config->_textSpeed*/, 0 },
	{ TOGGLE, NOFUNC, TM_NONE, NULL, SIX_STITLE_TOGGLE,	142, 20+40,	23, 19, 0 /*&_vm->_config->_useSubtitles*/, 0 },

	{ ARSGBUT, CLANG, TM_NONE, NULL, USE_POINTER,	230, 110,	23, 19, NULL, IX1_TICK1 },
	{ AAGBUT, RLANG, TM_NONE, NULL, USE_POINTER,	230, 140,	23, 19, NULL, IX1_CROSS1 }
};


static CONFBOX subtitlesBox5Flags[] =	{
	{ FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER,	15, 100,	56, 32, NULL, FIX_UK },
	{ FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER,	85, 100,	56, 32, NULL, FIX_FR },
	{ FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER,	155, 100,	56, 32, NULL, FIX_GR },
	{ FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER,	50, 137,	56, 32, NULL, FIX_IT },
	{ FRGROUP, NOFUNC, TM_NONE, NULL, USE_POINTER,	120, 137,	56, 32, NULL, FIX_SP },

	{ SLIDER, NOFUNC, TM_NONE, NULL, SIX_TSPEED_SLIDER,	142, 20,	100, 2, 0 /*&_vm->_config->_textSpeed*/, 0 },
	{ TOGGLE, NOFUNC, TM_NONE, NULL, SIX_STITLE_TOGGLE,	142, 20+40,	23, 19, 0 /*&_vm->_config->_useSubtitles*/, 0 },

	{ ARSGBUT, CLANG, TM_NONE, NULL, USE_POINTER,	230, 110,	23, 19, NULL, IX1_TICK1 },
	{ AAGBUT, RLANG, TM_NONE, NULL, USE_POINTER,	230, 140,	23, 19, NULL, IX1_CROSS1 }
};


/*-------------------------------------------------------------*\
| This is the quit confirmation 'menu'.				|
\*-------------------------------------------------------------*/

static CONFBOX t1QuitBox[] = {
#ifdef JAPAN
 { AAGBUT, IQUITGAME, TM_NONE, NULL, USE_POINTER,70, 44,	23, 19, NULL, IX_TICK1 },
 { AAGBUT, CLOSEWIN, TM_NONE, NULL, USE_POINTER,	30, 44,	23, 19, NULL, IX_CROSS1 }
#else
 { AAGBUT, IQUITGAME, TM_NONE, NULL, USE_POINTER,70, 28,	23, 19, NULL, IX1_TICK1 },
 { AAGBUT, CLOSEWIN, TM_NONE, NULL, USE_POINTER,	30, 28,	23, 19, NULL, IX1_CROSS1 }
#endif
};

static CONFBOX t2QuitBox[] = {
	{ AAGBUT, IQUITGAME, TM_NONE, NULL, 0,140, 78, BW, BH, NULL, IX2_TICK1 },
	{ AAGBUT, CLOSEWIN, TM_NONE, NULL, 0, 60, 78,  BW, BH, NULL, IX2_CROSS1 }
};

static CONFINIT t1ciQuit	= { 4, 2, 98, 53, false, t1QuitBox,	ARRAYSIZE(t1QuitBox),	SIX_QUIT_HEADING };
static CONFINIT t2ciQuit	= { 4, 2, 196, 53, false, t2QuitBox, sizeof(t2QuitBox)/sizeof(CONFBOX), SS_QUIT_HEADING };

#define quitBox (TinselV2 ? t2QuitBox : t1QuitBox)
#define ciQuit (TinselV2 ? t2ciQuit : t1ciQuit)

/***************************************************************************\
|************************    Startup and shutdown    ***********************|
\***************************************************************************/

static CONFBOX hopperBox1[] = {
	{ RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY,									 T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + (T2_BOX_HEIGHT + T2_BOX_V2_SEP),	 T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 2*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 3*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 4*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 5*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 6*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 7*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, HOPPER2, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 8*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },

	{ ARSGBUT, HOPPER2, TM_NONE, NULL, 0, 460, 100, BW, BH, NULL, IX2_TICK1 },
	{ AAGBUT, CLOSEWIN, TM_NONE, NULL, 0, 460, 100 + 100, BW, BH, NULL, IX2_CROSS1 }
};

static CONFINIT ciHopper1 = { 10, 6, 40, 16, true, hopperBox1, sizeof(hopperBox1) / sizeof(CONFBOX), SS_HOPPER1 };

static CONFBOX hopperBox2[] = {
	{ RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY,				T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + (T2_BOX_HEIGHT + T2_BOX_V2_SEP),	 T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 2*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 3*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 4*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 5*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 6*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 7*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },
	{ RGROUP, BF_CHANGESCENE, TM_STRINGNUM, NULL, 0, BOXX, BOXY + 8*(T2_BOX_HEIGHT + T2_BOX_V2_SEP), T2_EDIT_BOX2_WIDTH, T2_BOX_HEIGHT, NULL, 0 },

	{ ARSGBUT, BF_CHANGESCENE, TM_NONE, NULL, 0, 460, 50,  BW, BH, NULL, IX2_TICK1 },
	{ AAGBUT, CLOSEWIN, TM_NONE, NULL, 0, 460, 200, BW, BH, NULL, IX2_CROSS1 }
};

static CONFINIT ciHopper2 = { 10, 6, 40, 16, true, hopperBox2, sizeof(hopperBox2)/sizeof(CONFBOX), NO_HEADING };


/***************************************************************************\
|****************************    Top Window    *****************************|
\***************************************************************************/
static CONFBOX topwinBox[] = {
 { NOTHING, NOFUNC, TM_NONE, NULL, USE_POINTER, 0, 0, 0, 0, NULL, 0 }
};


static CONFINIT ciSubtitles	= { 10, 3, 20, 16, false, subtitlesBox,	ARRAYSIZE(subtitlesBox),	NO_HEADING };

static CONFINIT ciTopWin	= { 6, 5, 72, 23, false, topwinBox,	0,					NO_HEADING };

#define NOBOX (-1)

// Conf window globals
static struct {
	CONFBOX *box;
	int	NumBoxes;
	bool bExtraWin;
	uint32 ixHeading;
	bool editableRgroup;

	int	selBox;
	int	pointBox;	// Box pointed to on last call
	int	modifier;
	int	extraBase;
	int	numSaved;
} cd = {
	NULL, 0, false, 0, false,
	NOBOX, NOBOX, 0, 0, 0
};

// For editing save game names
static char g_sedit[SG_DESC_LEN+2];

#define HL1	0	// Hilight that moves with the cursor
#define HL2	1	// Hilight on selected RGROUP box
#define HL3	2	// Text on selected RGROUP box
#define NUMHL	3


// Data for button press/toggle effects
static struct {
	bool bButAnim;
	CONFBOX *box;
	bool press;		// true = button press; false = button toggle
} g_buttonEffect = { false, 0, false };


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

enum {
	IB_NONE			= -1,	//
	IB_UP			= -2,	// negative numbers returned
	IB_DOWN			= -3,	// by WhichMenuBox()
	IB_SLIDE		= -4,	//
	IB_SLIDE_UP		= -5,	//
	IB_SLIDE_DOWN	= -6	//
};

enum {
	HI_BIT		= ((uint)MIN_INT >> 1),	// The next to top bit
	IS_LEFT		= HI_BIT,
	IS_SLIDER	= (IS_LEFT >> 1),
	IS_RIGHT	= (IS_SLIDER >> 1),
	IS_MASK		= (IS_LEFT | IS_SLIDER | IS_RIGHT)
};

static int WhichMenuBox(int curX, int curY, bool bSlides);
static void SlideMSlider(int x, SSFN fn);
static OBJECT *AddObject(const FREEL *pfreel, int num);
static void AddBoxes(bool posnSlide);

static void ConfActionSpecial(int i);

static bool RePosition();

/*-------------------------------------------------------------------------*/
/***	Magic numbers	***/

#define M_SW	5	// Side width
#define M_TH	5	// Top height
#ifdef JAPAN
#define M_TOFF	6	// Title text Y offset from top
#define M_TBB	20	// Title box bottom Y offset
#else
#define M_TOFF	4	// Title text Y offset from top
#define M_TBB	14	// Title box bottom Y offset
#endif
#define M_SBL	26	// Scroll bar left X offset
#define M_SH	5	// Slider height (*)
#define M_SW	5	// Slider width (*)
#define M_SXOFF	9	// Slider X offset from right-hand side
#ifdef JAPAN
#define M_IUT	22	// Y offset of top of up arrow
#define M_IUB	30	// Y offset of bottom of up arrow
#else
#define M_IUT	16	// Y offset of top of up arrow
#define M_IUB	24	// Y offset of bottom of up arrow
#endif
#define M_IDT	10	// Y offset (from bottom) of top of down arrow
#define M_IDB	3	// Y offset (from bottom) of bottom of down arrow

#define START_ICONX	(TinselV2 ? 12 : (M_SW+1))			// } Relative offset of first icon
#define START_ICONY	(TinselV2 ? 40 : (M_TBB+M_TH+1))	// } within the inventory window

/*-------------------------------------------------------------------------*/


static bool LanguageChange() {
	LANGUAGE nLang = _vm->_config->_language;

	if ((_vm->getFeatures() & GF_USE_3FLAGS) || (_vm->getFeatures() & GF_USE_4FLAGS) || (_vm->getFeatures() & GF_USE_5FLAGS)) {
		// Languages: TXT_ENGLISH, TXT_FRENCH, TXT_GERMAN, TXT_ITALIAN, TXT_SPANISH
		// 5 flag versions include English
		int selected = (_vm->getFeatures() & GF_USE_5FLAGS) ? cd.selBox : cd.selBox + 1;
		// Make sure that a language flag has been selected. If the user has
		// changed the language speed slider and hasn't clicked on a flag, it
		// won't be selected.
		if (selected >= 0 && selected <= 4) {
			nLang = (LANGUAGE)selected;

			// 3 flag versions don't include Italian
			if (selected >= 3 && (_vm->getFeatures() & GF_USE_3FLAGS))
				nLang = TXT_SPANISH;
		}
	}

	if (nLang != _vm->_config->_language) {
		KillInventory();
		ChangeLanguage(nLang);
		_vm->_config->_language = nLang;
		return true;
	} else
		return false;
}

/**************************************************************************/
/*****************************  Scene Hopper ******************************/
/**************************************************************************/

/**
 * Read in the scene hopper data file and set the
 *  pointers to the data and scene count.
 */
static void PrimeSceneHopper() {
	Common::File f;
	char *pBuffer;
	uint32 vSize;

	// Open the file (it's on the CD)
	CdCD(Common::nullContext);
	if (!f.open(HOPPER_FILENAME))
		error(CANNOT_FIND_FILE, HOPPER_FILENAME);

	// Read in header
	if (f.readUint32LE() != CHUNK_SCENE_HOPPER)
		error(FILE_IS_CORRUPT, HOPPER_FILENAME);
	vSize = f.readUint32LE();

	// allocate a buffer for it all
	assert(g_pHopper == NULL);
	uint32 size = f.size() - 8;

	// make sure memory allocated
	pBuffer = (char *)malloc(size);
	if (pBuffer == NULL)
		// cannot alloc buffer for index
		error(NO_MEM, "Scene hopper data");

	// load data
	if (f.read(pBuffer, size) != size)
		// file must be corrupt if we get to here
		error(FILE_IS_CORRUPT, HOPPER_FILENAME);

	// Set data pointers
	g_pHopper = (PHOPPER)pBuffer;
	g_pEntries = (PHOPENTRY)(pBuffer + vSize);
	g_numScenes = vSize / sizeof(HOPPER);

	// close the file
	f.close();
}

/**
 * Free the scene hopper data file
 */
static void FreeSceneHopper() {
	free(g_pHopper);
	g_pHopper = NULL;
}

static void FirstScene(int first) {
	int	i;

	assert(g_numScenes && g_pHopper);

	if (g_bRemember) {
		assert(first == 0);
		first = g_lastChosenScene;
		g_bRemember = false;
	}

	// Force it to a sensible value
	if (first > g_numScenes - NUM_RGROUP_BOXES)
		first = g_numScenes - NUM_RGROUP_BOXES;
	if (first < 0)
		first = 0;

	// Fill in the rest
	for (i = 0; i < NUM_RGROUP_BOXES && i + first < g_numScenes; i++) {
		cd.box[i].textMethod = TM_STRINGNUM;
		cd.box[i].ixText = FROM_32(g_pHopper[i + first].hSceneDesc);
	}
	// Blank out the spare ones (if any)
	while (i < NUM_RGROUP_BOXES) {
		cd.box[i].textMethod = TM_NONE;
		cd.box[i++].ixText = 0;
	}

	cd.extraBase = first;
}

static void RememberChosenScene() {
	g_bRemember = true;
}

static void SetChosenScene() {
	g_lastChosenScene = cd.selBox + cd.extraBase;
	g_pChosenScene = &g_pHopper[cd.selBox + cd.extraBase];
}

static void FirstEntry(int first) {
	int	i;

	g_InvD[INV_MENU].hInvTitle = FROM_32(g_pChosenScene->hSceneDesc);

	// get number of entrances
	g_numEntries = FROM_32(g_pChosenScene->numEntries);

	// Force first to a sensible value
	if (first > g_numEntries-NUM_RGROUP_BOXES)
		first = g_numEntries-NUM_RGROUP_BOXES;
	if (first < 0)
		first = 0;

	for (i = 0; i < NUM_RGROUP_BOXES && i < g_numEntries; i++) {
		cd.box[i].textMethod = TM_STRINGNUM;
		cd.box[i].ixText = FROM_32(g_pEntries[FROM_32(g_pChosenScene->entryIndex) + i + first].hDesc);
	}
	// Blank out the spare ones (if any)
	while (i < NUM_RGROUP_BOXES) {
		cd.box[i].textMethod = TM_NONE;
		cd.box[i++].ixText = 0;
	}

	cd.extraBase = first;
}

static void HopAction() {
	PHOPENTRY pEntry = g_pEntries + FROM_32(g_pChosenScene->entryIndex) + cd.selBox + cd.extraBase;

	uint32 hScene = FROM_32(g_pChosenScene->hScene);
	uint32 eNumber = FROM_32(pEntry->eNumber);
	debugC(DEBUG_BASIC, kTinselDebugAnimations, "Scene hopper chose scene %xh,%d\n", hScene, eNumber);

	if (FROM_32(pEntry->flags) & fCall) {
		SaveScene(Common::nullContext);
		NewScene(Common::nullContext, g_pChosenScene->hScene, pEntry->eNumber, TRANS_FADE);
	}
	else if (FROM_32(pEntry->flags) & fHook)
		HookScene(hScene, eNumber, TRANS_FADE);
	else
		NewScene(Common::nullContext, hScene, eNumber, TRANS_CUT);
}

/**************************************************************************/
/******************** Some miscellaneous functions ************************/
/**************************************************************************/

/**
 * Delete all the objects in iconArray[]
 */
static void DumpIconArray() {
	for (int i = 0; i < MAX_ICONS; i++) {
		if (g_iconArray[i] != NULL) {
			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[i]);
			g_iconArray[i] = NULL;
		}
	}
}

/**
 * Delete all the objects in DobjArray[]
 */
static void DumpDobjArray() {
	for (int i = 0; i < MAX_WCOMP; i++) {
		if (g_DobjArray[i] != NULL) {
			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_DobjArray[i]);
			g_DobjArray[i] = NULL;
		}
	}
}

/**
 * Delete all the objects in objArray[]
 */
static void DumpObjArray() {
	for (int i = 0; i < MAX_WCOMP; i++) {
		if (g_objArray[i] != NULL) {
			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_objArray[i]);
			g_objArray[i] = NULL;
		}
	}
}

/**
 * Convert item ID number to pointer to item's compiled data
 * i.e. Image data and Glitter code.
 */
static INV_OBJECT *GetInvObject(int id) {
	INV_OBJECT *pObject = g_invObjects;

	for (int i = 0; i < g_numObjects; i++, pObject++) {
		if (pObject->id == id)
			return pObject;
	}

	error("GetInvObject(%d): Trying to manipulate undefined inventory icon", id);
}

/**
 * Returns true if the given id represents a valid inventory object
 */
bool GetIsInvObject(int id) {
	INV_OBJECT *pObject = g_invObjects;

	for (int i = 0; i < g_numObjects; i++, pObject++) {
		if (pObject->id == id)
			return true;
	}

	return false;
}

/**
 * Convert item ID number to index.
 */
static int GetObjectIndex(int id) {
	INV_OBJECT *pObject = g_invObjects;

	for (int i = 0; i < g_numObjects; i++, pObject++) {
		if (pObject->id == id)
			return i;
	}

	error("GetObjectIndex(%d): Trying to manipulate undefined inventory icon", id);
}

/**
 * Returns position of an item in one of the inventories.
 * The actual position is not important for the uses that this is put to.
 */
extern int InventoryPos(int num) {
	int	i;

	for (i = 0; i < g_InvD[INV_1].NoofItems; i++)	// First inventory
		if (g_InvD[INV_1].contents[i] == num)
			return i;

	for (i = 0; i < g_InvD[INV_2].NoofItems; i++)	// Second inventory
		if (g_InvD[INV_2].contents[i] == num)
			return i;

	if (g_heldItem == num)
		return INV_HELDNOTIN;	// Held, but not in either inventory

	return INV_NOICON;		// Not held, not in either inventory
}

extern bool IsInInventory(int object, int invnum) {
	assert(invnum == INV_1 || invnum == INV_2);

	for (int i = 0; i < g_InvD[invnum].NoofItems; i++)	// First inventory
		if (g_InvD[invnum].contents[i] == object)
			return true;

	return false;
}

/**
 * Returns which item is held (INV_NOICON (-1) if none)
 */
extern int WhichItemHeld() {
	return g_heldItem;
}

/**
 * Called from the cursor module when it re-initializes (at the start of
 * a new scene). For if we are holding something at scene-change time.
 */
extern void InventoryIconCursor(bool bNewItem) {

	if (g_heldItem != INV_NOICON) {
		if (TinselV2) {
			if (bNewItem) {
				int	objIndex = GetObjectIndex(g_heldItem);
				g_heldFilm = g_invFilms[objIndex];
			}
			SetAuxCursor(g_heldFilm);
		} else {
			INV_OBJECT *invObj = GetInvObject(g_heldItem);
			SetAuxCursor(invObj->hIconFilm);
		}
	}
}

/**
 * Returns true if the inventory is active.
 */
extern bool InventoryActive() {
	return (g_InventoryState == ACTIVE_INV);
}

extern int WhichInventoryOpen() {
	if (g_InventoryState != ACTIVE_INV)
		return 0;
	else
		return g_ino;
}


/**************************************************************************/
/************** Running inventory item's Glitter code *********************/
/**************************************************************************/

struct OP_INIT {
	INV_OBJECT *pinvo;
	TINSEL_EVENT	event;
	PLR_EVENT	bev;
	int	myEscape;
};

/**
 * Run inventory item's Glitter code
 */
static void ObjectProcess(CORO_PARAM, const void *param) {
	// COROUTINE
	CORO_BEGIN_CONTEXT;
		INT_CONTEXT *pic;
		int	ThisPointedWait;			//	Fix the 'repeated pressing bug'
	CORO_END_CONTEXT(_ctx);

	// get the stuff copied to process when it was created
	const OP_INIT *to = (const OP_INIT *)param;

	CORO_BEGIN_CODE(_ctx);

	if (!TinselV2)
		CORO_INVOKE_1(AllowDclick, to->bev);

	_ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, to->event, NOPOLY, 0, to->pinvo,
		to->myEscape);
	CORO_INVOKE_1(Interpret, _ctx->pic);

	if (to->event == POINTED) {
		_ctx->ThisPointedWait = ++g_PointedWaitCount;
		while (1) {
			CORO_SLEEP(1);
			int	x, y;
			GetCursorXY(&x, &y, false);
			if (InvItemId(x, y) != to->pinvo->id)
				break;

			// Fix the 'repeated pressing bug'
			if (_ctx->ThisPointedWait != g_PointedWaitCount)
				CORO_KILL_SELF();
		}

		_ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, UNPOINT, NOPOLY, 0, to->pinvo);
		CORO_INVOKE_1(Interpret, _ctx->pic);
	}

	CORO_END_CODE;
}

/**
 * Run inventory item's Glitter code
 */
static void InvTinselEvent(INV_OBJECT *pinvo, TINSEL_EVENT event, PLR_EVENT be, int index) {
	OP_INIT to = { pinvo, event, be, 0 };

	if (g_InventoryHidden || (TinselV2 && !pinvo->hScript))
		return;

	g_GlitterIndex = index;
	CoroScheduler.createProcess(PID_TCODE, ObjectProcess, &to, sizeof(to));
}

extern void ObjectEvent(CORO_PARAM, int objId, TINSEL_EVENT event, bool bWait, int myEscape, bool *result) {
	// COROUTINE
	CORO_BEGIN_CONTEXT;
		Common::PROCESS		*pProc;
		INV_OBJECT	*pInvo;
		OP_INIT		op;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	if (result) *result = false;
	_ctx->pInvo = GetInvObject(objId);
	if (!_ctx->pInvo->hScript)
		return;

	_ctx->op.pinvo = _ctx->pInvo;
	_ctx->op.event = event;
	_ctx->op.myEscape = myEscape;

	CoroScheduler.createProcess(PID_TCODE, ObjectProcess, &_ctx->op, sizeof(_ctx->op));

	if (bWait)
		CORO_INVOKE_2(WaitInterpret, _ctx->pProc, result);
	else if (result)
		*result = false;

	CORO_END_CODE;
}

/**************************************************************************/
/****************** Load/Save game specific functions *********************/
/**************************************************************************/

/**
 * Set first load/save file entry displayed.
 * Point Box[] text pointers to appropriate file descriptions.
 */
static void FirstFile(int first) {
	int	i, j;

	i = getList();

	cd.numSaved = i;

	if (first < 0)
		first = 0;
	else if (first > MAX_SAVED_FILES - NUM_RGROUP_BOXES)
		first = MAX_SAVED_FILES - NUM_RGROUP_BOXES;

	if (first == 0 && i < MAX_SAVED_FILES && cd.box == saveBox) {
		// Blank first entry for new save
		cd.box[0].boxText = NULL;
		cd.modifier = j = 1;
	} else {
		cd.modifier = j = 0;
	}

	for (i = first; j < NUM_RGROUP_BOXES; j++, i++) {
		cd.box[j].boxText = ListEntry(i, LE_DESC);
	}

	cd.extraBase = first;
}

/**
 * Save the game using filename from selected slot & current description.
 */

static void InvSaveGame() {
	if (cd.selBox != NOBOX) {
#ifndef JAPAN
		g_sedit[strlen(g_sedit)-1] = 0;	// Don't include the cursor!
#endif
		SaveGame(ListEntry(cd.selBox-cd.modifier+cd.extraBase, LE_NAME), g_sedit);
	}
}

/**
 * Load the selected saved game.
 */
static void InvLoadGame() {
	int	rGame;

	if (cd.selBox != NOBOX && (cd.selBox+cd.extraBase < cd.numSaved)) {
		rGame = cd.selBox;
		cd.selBox = NOBOX;
		if (g_iconArray[HL3] != NULL) {
			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL3]);
			g_iconArray[HL3] = NULL;
		}
		if (g_iconArray[HL2] != NULL) {
			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL2]);
			g_iconArray[HL2] = NULL;
		}
		if (g_iconArray[HL1] != NULL) {
			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL1]);
			g_iconArray[HL1] = NULL;
		}
		RestoreGame(rGame+cd.extraBase);
	}
}

/**
 * Edit the string in sedit[]
 * Returns true if the string was altered.
 */
#ifndef JAPAN
static bool UpdateString(const Common::KeyState &kbd) {
	int	cpos;

	if (!cd.editableRgroup)
		return false;

	cpos = strlen(g_sedit)-1;

	if (kbd.ascii == 0)
		return false;

	if (kbd.keycode == Common::KEYCODE_BACKSPACE) {
		if (!cpos)
			return false;
		g_sedit[cpos] = 0;
		cpos--;
		g_sedit[cpos] = CURSOR_CHAR;
		return true;
//	} else if (isalnum(c) || c == ',' || c == '.' || c == '\'' || (c == ' ' && cpos != 0)) {
	} else if (IsCharImage(GetTagFontHandle(), kbd.ascii) || (kbd.ascii == ' ' && cpos != 0)) {
		if (cpos == SG_DESC_LEN)
			return false;
		g_sedit[cpos] = kbd.ascii;
		cpos++;
		g_sedit[cpos] = CURSOR_CHAR;
		g_sedit[cpos+1] = 0;
		return true;
	}
	return false;
}
#endif

/**
 * Keystrokes get sent here when load/save screen is up.
 */
static bool InvKeyIn(const Common::KeyState &kbd) {
	if (kbd.keycode == Common::KEYCODE_PAGEUP ||
	    kbd.keycode == Common::KEYCODE_PAGEDOWN ||
	    kbd.keycode == Common::KEYCODE_HOME ||
	    kbd.keycode == Common::KEYCODE_END)
		return true;	// Key needs processing

	if (kbd.keycode == 0 && kbd.ascii == 0) {
		;
	} else if (kbd.keycode == Common::KEYCODE_RETURN) {
		return true;	// Key needs processing
	} else if (kbd.keycode == Common::KEYCODE_ESCAPE) {
		return true;	// Key needs processing
	} else {
#ifndef JAPAN
		if (UpdateString(kbd)) {
			/*
			* Delete display of text currently being edited,
			* and replace it with freshly edited text.
			*/
			if (g_iconArray[HL3] != NULL) {
				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL3]);
				g_iconArray[HL3] = NULL;
			}
			g_iconArray[HL3] = ObjectTextOut(
				GetPlayfieldList(FIELD_STATUS), g_sedit, 0,
				g_InvD[g_ino].inventoryX + cd.box[cd.selBox].xpos + 2,
				g_InvD[g_ino].inventoryY + cd.box[cd.selBox].ypos + TYOFF,
				GetTagFontHandle(), 0);
			if (MultiRightmost(g_iconArray[HL3]) > MAX_NAME_RIGHT) {
				MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL3]);
				UpdateString(Common::KeyState(Common::KEYCODE_BACKSPACE));
				g_iconArray[HL3] = ObjectTextOut(
					GetPlayfieldList(FIELD_STATUS), g_sedit, 0,
					g_InvD[g_ino].inventoryX + cd.box[cd.selBox].xpos + 2,
					g_InvD[g_ino].inventoryY + cd.box[cd.selBox].ypos + TYOFF,
					GetTagFontHandle(), 0);
			}
			MultiSetZPosition(g_iconArray[HL3], Z_INV_ITEXT + 2);
		}
#endif
	}
	return false;
}

/**
 * Highlights selected box.
 * If it's editable (save game), copy existing description and add a cursor.
 */
static void Select(int i, bool force) {
#ifdef JAPAN
	time_t		secs_now;
	struct tm	*time_now;
#endif

	i &= ~IS_MASK;

	if (cd.selBox == i && !force)
		return;

	cd.selBox = i;

	// Clear previous selected highlight and text
	if (g_iconArray[HL2] != NULL) {
		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL2]);
		g_iconArray[HL2] = NULL;
	}
	if (g_iconArray[HL3] != NULL) {
		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL3]);
		g_iconArray[HL3] = NULL;
	}

	// New highlight box
	switch (cd.box[i].boxType) {
	case RGROUP:
		g_iconArray[HL2] = RectangleObject(BgPal(),
			(TinselV2 ? HighlightColor() : COL_HILIGHT), cd.box[i].w, cd.box[i].h);
		MultiInsertObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL2]);
		MultiSetAniXY(g_iconArray[HL2],
		g_InvD[g_ino].inventoryX + cd.box[i].xpos,
		g_InvD[g_ino].inventoryY + cd.box[i].ypos);

		// Z-position of box, and add edit text if appropriate
		if (cd.editableRgroup) {
			MultiSetZPosition(g_iconArray[HL2], Z_INV_ITEXT+1);

			if (TinselV2) {
				assert(cd.box[i].textMethod == TM_POINTER);
			} else {
				assert(cd.box[i].ixText == USE_POINTER);
			}
#ifdef JAPAN
			// Current date and time
			time(&secs_now);
			time_now = localtime(&secs_now);
			strftime(g_sedit, SG_DESC_LEN, "%D %H:%M", time_now);
#else
			// Current description with cursor appended
			if (cd.box[i].boxText != NULL) {
				Common::strlcpy(g_sedit, cd.box[i].boxText, SG_DESC_LEN+2);
				Common::strlcat(g_sedit, sCursor, SG_DESC_LEN+2);
			} else {
				Common::strlcpy(g_sedit, sCursor, SG_DESC_LEN+2);
			}
#endif

			g_iconArray[HL3] = ObjectTextOut(
				GetPlayfieldList(FIELD_STATUS), g_sedit, 0,
				g_InvD[g_ino].inventoryX + cd.box[i].xpos + 2,
#ifdef JAPAN
				g_InvD[g_ino].inventoryY + cd.box[i].ypos + 2,
#else
				g_InvD[g_ino].inventoryY + cd.box[i].ypos + TYOFF,
#endif
				GetTagFontHandle(), 0);
			MultiSetZPosition(g_iconArray[HL3], Z_INV_ITEXT + 2);
		} else {
			MultiSetZPosition(g_iconArray[HL2], Z_INV_ICONS + 1);
		}

		_vm->divertKeyInput(InvKeyIn);

		break;

	case FRGROUP:
		g_iconArray[HL2] = RectangleObject(BgPal(), COL_HILIGHT, cd.box[i].w+6, cd.box[i].h+6);
		MultiInsertObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL2]);
		MultiSetAniXY(g_iconArray[HL2],
		g_InvD[g_ino].inventoryX + cd.box[i].xpos - 2,
		g_InvD[g_ino].inventoryY + cd.box[i].ypos - 2);
		MultiSetZPosition(g_iconArray[HL2], Z_INV_BRECT+1);

		break;

	default:
		break;
	}
}


/**************************************************************************/
/***/
/**************************************************************************/

/**
 * Stop holding an item.
 */
extern void DropItem(int item) {
	if (g_heldItem == item) {
		g_heldItem = INV_NOICON;		// Item not held
		DelAuxCursor();			// no longer aux cursor
	}

	// Redraw contents - held item was not displayed as a content.
	g_ItemsChanged = true;
}

/**
 * Clears the specified inventory
 */
extern void ClearInventory(int invno) {
	assert(invno == INV_1 || invno == INV_2);

	g_InvD[invno].NoofItems = 0;
	memset(g_InvD[invno].contents, 0, sizeof(g_InvD[invno].contents));
}

/**
 * Stick the item into an inventory list (contents[]), and hold the
 * item if requested.
 */
extern void AddToInventory(int invno, int icon, bool hold) {
	int i;
	bool bOpen;
	INV_OBJECT *invObj;

	// Validate trying to add to a legal inventory
	assert(invno == INV_1 || invno == INV_2 || invno == INV_CONV
		|| invno == INV_OPEN || (invno == INV_DEFAULT && TinselV2));

	if (invno == INV_OPEN) {
		assert(g_InventoryState == ACTIVE_INV && (g_ino == INV_1 || g_ino == INV_2)); // addopeninv() with inventry not open
		invno = g_ino;
		bOpen = true;

		// Make sure it doesn't get in both!
		RemFromInventory(g_ino == INV_1 ? INV_2 : INV_1, icon);
	} else {
		bOpen = false;

		if (TinselV2 && invno == INV_DEFAULT) {
			invObj = GetInvObject(icon);
			if (invObj->attribute & DEFINV2)
				invno = INV_2;
			else if (invObj->attribute & DEFINV1)
				invno = INV_1;
			else
				invno = SysVar(SV_DEFAULT_INV);
		}
	}

	if (invno == INV_1)
		RemFromInventory(INV_2, icon);
	else if (invno == INV_2)
		RemFromInventory(INV_1, icon);

	// See if it's already there
	for (i = 0; i < g_InvD[invno].NoofItems; i++) {
		if (g_InvD[invno].contents[i] == icon)
			break;
	}

	// Add it if it isn't already there
	if (i == g_InvD[invno].NoofItems) {
		if (!bOpen) {
			if (invno == INV_CONV) {
				if (TinselV2) {
					int nei;

					// Count how many current contents have end attribute
					for (i = 0, nei = 0; i < g_InvD[INV_CONV].NoofItems; i++) {
						invObj = GetInvObject(g_InvD[INV_CONV].contents[i]);
						if (invObj->attribute & CONVENDITEM)
							nei++;
					}

					// For conversation, insert before end icons
					memmove(&g_InvD[INV_CONV].contents[i-nei+1],
						&g_InvD[INV_CONV].contents[i-nei], nei * sizeof(int));
					g_InvD[INV_CONV].contents[i - nei] = icon;
					g_InvD[INV_CONV].NoofItems++;
					g_InvD[INV_CONV].NoofHicons = g_InvD[INV_CONV].NoofItems;

					// Get the window to re-position
					g_bMoveOnUnHide = true;
				} else {
					// For conversation, insert before last icon
					// which will always be the goodbye icon
					g_InvD[invno].contents[g_InvD[invno].NoofItems] = g_InvD[invno].contents[g_InvD[invno].NoofItems-1];
					g_InvD[invno].contents[g_InvD[invno].NoofItems-1] = icon;
					g_InvD[invno].NoofItems++;
				}
			} else {
				g_InvD[invno].contents[g_InvD[invno].NoofItems++] = icon;
			}
			g_ItemsChanged = true;
		} else {
			// It could be that the index is beyond what you'd expect
			// as delinv may well have been called
			if (g_GlitterIndex < g_InvD[invno].NoofItems) {
				memmove(&g_InvD[invno].contents[g_GlitterIndex + 1],
					&g_InvD[invno].contents[g_GlitterIndex],
					(g_InvD[invno].NoofItems - g_GlitterIndex) * sizeof(int));
				g_InvD[invno].contents[g_GlitterIndex] = icon;
			} else {
				g_InvD[invno].contents[g_InvD[invno].NoofItems] = icon;
			}
			g_InvD[invno].NoofItems++;
		}

		// Move here after bug on Japenese DW1
		g_ItemsChanged = true;
	}

	// Hold it if requested
	if (hold)
		HoldItem(icon);
}

/**
 * Take the item from the inventory list (contents[]).
 * Return FALSE if item wasn't present, true if it was.
 */
extern bool RemFromInventory(int invno, int icon) {
	int i;

	assert(invno == INV_1 || invno == INV_2 || invno == INV_CONV); // Trying to delete from illegal inventory

	// See if it's there
	for (i = 0; i < g_InvD[invno].NoofItems; i++) {
		if (g_InvD[invno].contents[i] == icon)
			break;
	}

	if (i == g_InvD[invno].NoofItems)
		return false;			// Item wasn't there
	else {
		memmove(&g_InvD[invno].contents[i], &g_InvD[invno].contents[i+1], (g_InvD[invno].NoofItems-i)*sizeof(int));
		g_InvD[invno].NoofItems--;

		if (TinselV2 && invno == INV_CONV) {
			g_InvD[INV_CONV].NoofHicons = g_InvD[invno].NoofItems;

			// Get the window to re-position
			g_bMoveOnUnHide = true;
		}

		g_ItemsChanged = true;
		return true;			// Item removed
	}
}

/**
 * If the item is not already held, hold it.
 */
extern void HoldItem(int item, bool bKeepFilm) {
	INV_OBJECT *invObj;

	if (g_heldItem != item) {
		if (TinselV2 && (g_heldItem != NOOBJECT)) {
			// No longer holding previous item
			DelAuxCursor();	 // no longer aux cursor

			// If old held object is not in an inventory, and
			// has a default, stick it in its default inventory.
			if (!IsInInventory(g_heldItem, INV_1) && !IsInInventory(g_heldItem, INV_2)) {
				invObj = GetInvObject(g_heldItem);

				if (invObj->attribute & DEFINV1)
					AddToInventory(INV_1, g_heldItem);
				else if (invObj->attribute & DEFINV2)
					AddToInventory(INV_2, g_heldItem);
				else
					// Hook for definable default inventory
					AddToInventory(INV_1, g_heldItem);
			}

		} else if (!TinselV2) {
			if (item == INV_NOICON && g_heldItem != INV_NOICON)
				DelAuxCursor();			// no longer aux cursor

			if (item != INV_NOICON) {
				invObj = GetInvObject(item);
				SetAuxCursor(invObj->hIconFilm);	// and is aux. cursor
			}

			// WORKAROUND: If a held item is being removed that's not in either inventory (i.e. it was picked up
			// but never put in them), then when removing it from being held, drop it in the luggage
			if (g_heldItem != INV_NOICON && InventoryPos(g_heldItem) == INV_HELDNOTIN)
				AddToInventory(INV_1, g_heldItem);
		}

		g_heldItem = item;			// Item held

		if (TinselV2) {
			InventoryIconCursor(!bKeepFilm);

			// Redraw contents - held item not displayed as a content.
			g_ItemsChanged = true;
		}
	}

	if (!TinselV2)
		// Redraw contents - held item not displayed as a content.
		g_ItemsChanged = true;
}

/**************************************************************************/
/***/
/**************************************************************************/

enum {	I_NOTIN, I_HEADER, I_BODY,
	I_TLEFT, I_TRIGHT, I_BLEFT, I_BRIGHT,
	I_TOP, I_BOTTOM, I_LEFT, I_RIGHT,
	I_UP, I_SLIDE_UP, I_SLIDE, I_SLIDE_DOWN, I_DOWN,
	I_ENDCHANGE
};

#define EXTRA	1	// This was introduced when we decided to increase
			// the active area of the borders for re-sizing.

/*---------------------------------*/
#define LeftX	g_InvD[g_ino].inventoryX
#define TopY	g_InvD[g_ino].inventoryY
/*---------------------------------*/

/**
 * Work out which area of the inventory window the cursor is in.
 *
 * This used to be worked out with appropriately defined magic numbers.
 * Then the graphic changed and I got it right again. Then the graphic
 * changed and I got fed up of faffing about. It's probably easier just
 * to rework all this.
 */
static int InvArea(int x, int y) {
	if (TinselV2) {
		int RightX = MultiRightmost(g_RectObject) - NM_BG_SIZ_X - NM_BG_POS_X - NM_RS_R_INSET;
		int BottomY = MultiLowest(g_RectObject) - NM_BG_SIZ_Y - NM_BG_POS_Y - NM_RS_B_INSET;

		// Outside the whole rectangle?
		if (x <= LeftX || x > RightX || y <= TopY || y > BottomY)
			return I_NOTIN;

		// The bottom line
		if (y > BottomY - NM_RS_THICKNESS) {
			// Below top of bottom line?
			if (x <= LeftX + NM_RS_THICKNESS)
				return I_BLEFT;		// Bottom left corner
			else if (x > RightX - NM_RS_THICKNESS)
				return I_BRIGHT;	// Bottom right corner
			else
				return I_BOTTOM;	// Just plain bottom
		}

		// The top line
		if (y <= TopY + NM_RS_THICKNESS) {
			// Above bottom of top line?
			if (x <= LeftX + NM_RS_THICKNESS)
				return I_TLEFT;		// Top left corner
			else if (x > RightX - NM_RS_THICKNESS)
				return I_TRIGHT;	// Top right corner
			else
				return I_TOP;		// Just plain top
		}

		// Sides
		if (x <= LeftX + NM_RS_THICKNESS)	// Left of right of left side?
			return I_LEFT;
		else if (x > RightX - NM_RS_THICKNESS)	// Right of left of right side?
			return I_RIGHT;

		// In the move area?
		if (y < TopY + NM_MOVE_AREA_B_Y)
			return I_HEADER;

		// Scroll bits
		if (!(g_ino == INV_MENU && cd.bExtraWin)) {
			if (x > RightX - NM_SLIDE_INSET && x <= RightX - NM_SLIDE_INSET + NM_SLIDE_THICKNESS) {
				if (y > TopY + NM_UP_ARROW_TOP && y < TopY + NM_UP_ARROW_BOTTOM)
					return I_UP;
				if (y > BottomY - NM_DN_ARROW_TOP && y <= BottomY - NM_DN_ARROW_BOTTOM)
					return I_DOWN;

				/* '3' is a magic adjustment with no apparent sense */

				if (y >= TopY + g_sliderYmin - 3 && y < TopY + g_sliderYmax + NM_SLH) {
					if (y < TopY + g_sliderYpos - 3)
						return I_SLIDE_UP;
					if (y < TopY + g_sliderYpos + NM_SLH - 3)
						return I_SLIDE;
					else
						return I_SLIDE_DOWN;
				}
			}
		}
	} else {
		int RightX = MultiRightmost(g_RectObject) + 1;
		int BottomY = MultiLowest(g_RectObject) + 1;

		// Outside the whole rectangle?
		if (x <= LeftX - EXTRA || x > RightX + EXTRA
		|| y <= TopY - EXTRA || y > BottomY + EXTRA)
			return I_NOTIN;

		// The bottom line
		if (y > BottomY - 2 - EXTRA) {		// Below top of bottom line?
			if (x <= LeftX + 2 + EXTRA)
				return I_BLEFT;		// Bottom left corner
			else if (x > RightX - 2 - EXTRA)
				return I_BRIGHT;	// Bottom right corner
			else
				return I_BOTTOM;	// Just plain bottom
		}

		// The top line
		if (y <= TopY + 2 + EXTRA) {		// Above bottom of top line?
			if (x <= LeftX + 2 + EXTRA)
				return I_TLEFT;		// Top left corner
			else if (x > RightX - 2 - EXTRA)
				return I_TRIGHT;	// Top right corner
			else
				return I_TOP;		// Just plain top
		}

		// Sides
		if (x <= LeftX + 2 + EXTRA)		// Left of right of left side?
			return I_LEFT;
		else if (x > RightX - 2 - EXTRA)		// Right of left of right side?
			return I_RIGHT;

		// From here down still needs fixing up properly
		/*
		 * In the move area?
		 */
		if (g_ino != INV_CONF
		&& x >= LeftX + M_SW - 2 && x <= RightX - M_SW + 3 &&
		   y >= TopY + M_TH - 2  && y < TopY + M_TBB + 2)
			return I_HEADER;

		/*
		 * Scroll bits
		 */
		if (!(g_ino == INV_CONF && cd.bExtraWin)) {
			if (x > RightX - NM_SLIDE_INSET && x <= RightX - NM_SLIDE_INSET + NM_SLIDE_THICKNESS) {
				if (y > TopY + M_IUT + 1 && y < TopY + M_IUB - 1)
					return I_UP;
				if (y > BottomY - M_IDT + 4 && y <= BottomY - M_IDB + 1)
					return I_DOWN;

				if (y >= TopY + g_sliderYmin && y < TopY + g_sliderYmax + M_SH) {
					if (y < TopY + g_sliderYpos)
						return I_SLIDE_UP;
					if (y < TopY + g_sliderYpos + M_SH)
						return I_SLIDE;
					else
						return I_SLIDE_DOWN;
				}
			}
		}
	}

	return I_BODY;
}

/**
 * Returns the id of the icon displayed under the given position.
 * Also return co-ordinates of items tag display position, if requested.
 */
extern int InvItem(int *x, int *y, bool update) {
	int itop, ileft;
	int row, col;
	int item;
	int IconsX;

	itop = g_InvD[g_ino].inventoryY + START_ICONY;

	IconsX = g_InvD[g_ino].inventoryX + START_ICONX;

	for (item = g_InvD[g_ino].FirstDisp, row = 0; row < g_InvD[g_ino].NoofVicons; row++) {
		ileft = IconsX;

		for (col = 0; col < g_InvD[g_ino].NoofHicons; col++, item++) {
			if (*x >= ileft && *x < ileft + ITEM_WIDTH &&
			   *y >= itop  && *y < itop + ITEM_HEIGHT) {
				if (update) {
					*x = ileft + ITEM_WIDTH/2;
					*y = itop /*+ ITEM_HEIGHT/4*/;
				}
				return item;
			}

			ileft += ITEM_WIDTH + 1;
		}
		itop += ITEM_HEIGHT + 1;
	}
	return INV_NOICON;
}

static int InvItem(Common::Point &coOrds, bool update) {
	int x = coOrds.x;
	int y = coOrds.y;
	return InvItem(&x, &y, update);
	//coOrds.x = x;
	//coOrds.y = y;
}

/**
 * Returns the id of the icon displayed under the given position.
 */
int InvItemId(int x, int y) {
	int itop, ileft;
	int row, col;
	int item;

	if (g_InventoryHidden || g_InventoryState == IDLE_INV)
		return INV_NOICON;

	itop = g_InvD[g_ino].inventoryY + START_ICONY;

	int IconsX = g_InvD[g_ino].inventoryX + START_ICONX;

	for (item = g_InvD[g_ino].FirstDisp, row = 0; row < g_InvD[g_ino].NoofVicons; row++) {
		ileft = IconsX;

		for (col = 0; col < g_InvD[g_ino].NoofHicons; col++, item++) {
			if (x >= ileft && x < ileft + ITEM_WIDTH &&
			   y >= itop  && y < itop + ITEM_HEIGHT) {
				return g_InvD[g_ino].contents[item];
			}

			ileft += ITEM_WIDTH + 1;
		}
		itop += ITEM_HEIGHT + 1;
	}
	return INV_NOICON;
}

/**
 * Finds which box the cursor is in.
 */
static int WhichMenuBox(int curX, int curY, bool bSlides) {
	if (bSlides) {
		for (int i = 0; i < g_numMdSlides; i++) {
			if (curY > MultiHighest(g_mdSlides[i].obj) && curY < MultiLowest(g_mdSlides[i].obj)
			&& curX > MultiLeftmost(g_mdSlides[i].obj) && curX < MultiRightmost(g_mdSlides[i].obj))
				return g_mdSlides[i].num | IS_SLIDER;
		}
	}

	curX -= g_InvD[g_ino].inventoryX;
	curY -= g_InvD[g_ino].inventoryY;

	for (int i = 0; i < cd.NumBoxes; i++) {
		switch (cd.box[i].boxType) {
		case SLIDER:
			if (bSlides) {
				if (curY >= cd.box[i].ypos+MD_YBUTTOP && curY < cd.box[i].ypos+MD_YBUTBOT) {
					if (curX >= cd.box[i].xpos+MD_XLBUTL && curX < cd.box[i].xpos+MD_XLBUTR)
						return i | IS_LEFT;
					if (curX >= cd.box[i].xpos+MD_XRBUTL && curX < cd.box[i].xpos+MD_XRBUTR)
						return i | IS_RIGHT;
				}
			}
			break;

		case AAGBUT:
		case ARSGBUT:
		case TOGGLE:
		case TOGGLE1:
		case TOGGLE2:
		case FLIP:
			if (curY > cd.box[i].ypos && curY < cd.box[i].ypos + cd.box[i].h
			&& curX > cd.box[i].xpos && curX < cd.box[i].xpos + cd.box[i].w)
				return i;
			break;

		case ROTATE:
			if (g_bNoLanguage)
				break;

			if (curY > cd.box[i].ypos && curY < cd.box[i].ypos + cd.box[i].h) {
				// Left one?
				if (curX > cd.box[i].xpos-ROTX1 && curX < cd.box[i].xpos-ROTX1 + cd.box[i].w) {
					cd.box[i].bi = IX2_LEFT1;
					return i;
				}
				// Right one?
				if (curX > cd.box[i].xpos+ROTX1 && curX < cd.box[i].xpos+ROTX1 + cd.box[i].w) {
					cd.box[i].bi = IX2_RIGHT1;
					return i;
				}
			}
			break;

		default:
			// 'Normal' box
			if (curY >= cd.box[i].ypos && curY < cd.box[i].ypos + cd.box[i].h
			&& curX >= cd.box[i].xpos && curX < cd.box[i].xpos + cd.box[i].w)
				return i;
			break;
		}
	}

	// Slider on extra window
	if (cd.bExtraWin) {
		const Common::Rect r = TinselV2 ?
			Common::Rect(411, 46, 425, 339) :
			Common::Rect(20 + 181, 24 + 2, 20 + 181 + 8, 24 + 139 + 5);

		if (r.contains(curX, curY)) {

			if (curY < (r.top + (TinselV2 ? 18 : 5)))
				return IB_UP;
			else if (curY > (r.bottom - (TinselV2 ? 18 : 5)))
				return IB_DOWN;
			else if (curY + g_InvD[g_ino].inventoryY < g_sliderYpos)
				return IB_SLIDE_UP;
			else if (curY + g_InvD[g_ino].inventoryY >= g_sliderYpos + NM_SLH)
				return IB_SLIDE_DOWN;
			else
				return IB_SLIDE;
		}
	}

	return IB_NONE;
}

/**************************************************************************/
/***/
/**************************************************************************/

#define ROTX1 60	// Rotate button's offsets from the center

/**
 * InvBoxes
 */
static void InvBoxes(bool InBody, int curX, int curY) {
	static int rotateIndex = -1;	// FIXME: Avoid non-const global vars
	int	index;			// Box pointed to on this call
	const FILM *pfilm;

	// Find out which icon is currently pointed to
	if (!InBody)
		index = -1;
	else {
		index = WhichMenuBox(curX, curY, false);
	}

	// If no icon pointed to, or points to (logical position of)
	// currently held icon, then no icon is pointed to!
	if (index < 0) {
		// unhigh-light box (if one was)
		cd.pointBox = NOBOX;
		if (g_iconArray[HL1] != NULL) {
			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL1]);
			g_iconArray[HL1] = NULL;
		}
	} else if (index != cd.pointBox) {
		cd.pointBox = index;
		// A new box is pointed to - high-light it
		if (g_iconArray[HL1] != NULL) {
			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL1]);
			g_iconArray[HL1] = NULL;
		}
		if ((cd.box[cd.pointBox].boxType == ARSBUT && cd.selBox != NOBOX) ||
///* I don't agree */ cd.box[cd.pointBox].boxType == RGROUP ||
		    cd.box[cd.pointBox].boxType == AATBUT ||
		    cd.box[cd.pointBox].boxType == AABUT) {
			g_iconArray[HL1] = RectangleObject(BgPal(),
				(TinselV2 ? HighlightColor() : COL_HILIGHT),
				cd.box[cd.pointBox].w, cd.box[cd.pointBox].h);
			MultiInsertObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL1]);
			MultiSetAniXY(g_iconArray[HL1],
				g_InvD[g_ino].inventoryX + cd.box[cd.pointBox].xpos,
				g_InvD[g_ino].inventoryY + cd.box[cd.pointBox].ypos);
			MultiSetZPosition(g_iconArray[HL1], Z_INV_ICONS+1);
		} else if (cd.box[cd.pointBox].boxType == AAGBUT ||
				cd.box[cd.pointBox].boxType == ARSGBUT ||
				cd.box[cd.pointBox].boxType == TOGGLE ||
				cd.box[cd.pointBox].boxType == TOGGLE1 ||
				cd.box[cd.pointBox].boxType == TOGGLE2) {
			pfilm = (const FILM *)LockMem(g_hWinParts);

			g_iconArray[HL1] = AddObject(&pfilm->reels[cd.box[cd.pointBox].bi+HIGRAPH], -1);
			MultiSetAniXY(g_iconArray[HL1],
				g_InvD[g_ino].inventoryX + cd.box[cd.pointBox].xpos,
				g_InvD[g_ino].inventoryY + cd.box[cd.pointBox].ypos);
			MultiSetZPosition(g_iconArray[HL1], Z_INV_ICONS+1);
		} else if (cd.box[cd.pointBox].boxType == ROTATE) {
			if (g_bNoLanguage)
				return;

			pfilm = (const FILM *)LockMem(g_hWinParts);

			rotateIndex = cd.box[cd.pointBox].bi;
			if (rotateIndex == IX2_LEFT1) {
				g_iconArray[HL1] = AddObject(&pfilm->reels[IX2_LEFT2], -1 );
				MultiSetAniXY(g_iconArray[HL1],
					g_InvD[g_ino].inventoryX + cd.box[cd.pointBox].xpos - ROTX1,
					g_InvD[g_ino].inventoryY + cd.box[cd.pointBox].ypos);
				MultiSetZPosition(g_iconArray[HL1], Z_INV_ICONS+1);
			} else if (rotateIndex == IX2_RIGHT1) {
				g_iconArray[HL1] = AddObject(&pfilm->reels[IX2_RIGHT2], -1);
				MultiSetAniXY(g_iconArray[HL1],
					g_InvD[g_ino].inventoryX + cd.box[cd.pointBox].xpos + ROTX1,
					g_InvD[g_ino].inventoryY + cd.box[cd.pointBox].ypos);
				MultiSetZPosition(g_iconArray[HL1], Z_INV_ICONS + 1);
			}
		}
	}
}

static void ButtonPress(CORO_PARAM, CONFBOX *box) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	const FILM *pfilm;

	assert(box->boxType == AAGBUT || box->boxType == ARSGBUT);

	// Replace highlight image with normal image
	pfilm = (const FILM *)LockMem(g_hWinParts);
	if (g_iconArray[HL1] != NULL)
		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL1]);
	pfilm = (const FILM *)LockMem(g_hWinParts);
	g_iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1);
	MultiSetAniXY(g_iconArray[HL1], g_InvD[g_ino].inventoryX + box->xpos, g_InvD[g_ino].inventoryY + box->ypos);
	MultiSetZPosition(g_iconArray[HL1], Z_INV_ICONS+1);

	// Hold normal image for 1 frame
	CORO_SLEEP(1);
	if (g_iconArray[HL1] == NULL)
		return;

	// Replace normal image with depresses image
	pfilm = (const FILM *)LockMem(g_hWinParts);
	MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL1]);
	g_iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1);
	MultiSetAniXY(g_iconArray[HL1], g_InvD[g_ino].inventoryX + box->xpos, g_InvD[g_ino].inventoryY + box->ypos);
	MultiSetZPosition(g_iconArray[HL1], Z_INV_ICONS+1);

	// Hold depressed image for 2 frames
	CORO_SLEEP(2);
	if (g_iconArray[HL1] == NULL)
		return;

	// Replace depressed image with normal image
	pfilm = (const FILM *)LockMem(g_hWinParts);
	MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL1]);
	g_iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1);
	MultiSetAniXY(g_iconArray[HL1], g_InvD[g_ino].inventoryX + box->xpos, g_InvD[g_ino].inventoryY + box->ypos);
	MultiSetZPosition(g_iconArray[HL1], Z_INV_ICONS+1);

	CORO_SLEEP(1);

	CORO_END_CODE;
}

static void ButtonToggle(CORO_PARAM, CONFBOX *box) {
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	const FILM *pfilm;

	assert((box->boxType == TOGGLE) || (box->boxType == TOGGLE1)
		|| (box->boxType == TOGGLE2));

	// Remove hilight image
	if (g_iconArray[HL1] != NULL) {
		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL1]);
		g_iconArray[HL1] = NULL;
	}

	// Hold normal image for 1 frame
	CORO_SLEEP(1);
	if (g_InventoryState != ACTIVE_INV)
		return;

	// Add depressed image
	pfilm = (const FILM *)LockMem(g_hWinParts);
	g_iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1);
	MultiSetAniXY(g_iconArray[HL1], g_InvD[g_ino].inventoryX + box->xpos, g_InvD[g_ino].inventoryY + box->ypos);
	MultiSetZPosition(g_iconArray[HL1], Z_INV_ICONS+1);

	// Hold depressed image for 1 frame
	CORO_SLEEP(1);
	if (g_iconArray[HL1] == NULL)
		return;

	// Toggle state
	(*box->ival) = *(box->ival) ^ 1;	// XOR with true
	box->bi = *(box->ival) ? IX_TICK1 : IX_CROSS1;
	AddBoxes(false);
	// Keep highlight (e.g. flag)
	if (cd.selBox != NOBOX)
		Select(cd.selBox, true);

	// New state, depressed image
	pfilm = (const FILM *)LockMem(g_hWinParts);
	if (g_iconArray[HL1] != NULL)
		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL1]);
	g_iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1);
	MultiSetAniXY(g_iconArray[HL1], g_InvD[g_ino].inventoryX + box->xpos, g_InvD[g_ino].inventoryY + box->ypos);
	MultiSetZPosition(g_iconArray[HL1], Z_INV_ICONS+1);

	// Hold new depressed image for 1 frame
	CORO_SLEEP(1);
	if (g_iconArray[HL1] == NULL)
		return;

	// New state, normal
	MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL1]);
	g_iconArray[HL1] = NULL;

	// Hold normal image for 1 frame
	CORO_SLEEP(1);
	if (g_InventoryState != ACTIVE_INV)
		return;

	// New state, highlighted
	pfilm = (const FILM *)LockMem(g_hWinParts);
	if (g_iconArray[HL1] != NULL)
		MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[HL1]);
	g_iconArray[HL1] = AddObject(&pfilm->reels[box->bi+HIGRAPH], -1);
	MultiSetAniXY(g_iconArray[HL1], g_InvD[g_ino].inventoryX + box->xpos, g_InvD[g_ino].inventoryY + box->ypos);
	MultiSetZPosition(g_iconArray[HL1], Z_INV_ICONS+1);

	CORO_END_CODE;
}

/**
 * Monitors for POINTED event for inventory icons.
 */
static void InvLabels(bool InBody, int aniX, int aniY) {
	int	index;				// Icon pointed to on this call
	INV_OBJECT *invObj;

	// Find out which icon is currently pointed to
	if (!InBody)
		index = INV_NOICON;
	else {
		index = InvItem(&aniX, &aniY, false);
		if (index != INV_NOICON) {
			if (index >= g_InvD[g_ino].NoofItems)
				index = INV_NOICON;
			else
				index = g_InvD[g_ino].contents[index];
		}
	}

	// If no icon pointed to, or points to (logical position of)
	// currently held icon, then no icon is pointed to!
	if (index == INV_NOICON || index == g_heldItem) {
		g_pointedIcon = INV_NOICON;
	} else if (index != g_pointedIcon) {
		// A new icon is pointed to - run its script with POINTED event
		invObj = GetInvObject(index);
		if (invObj->hScript)
			InvTinselEvent(invObj, POINTED, PLR_NOEVENT, index);
		g_pointedIcon = index;
	}
}

/**************************************************************************/
/***/
/**************************************************************************/

/**
 * All to do with the slider.
 * I can't remember how it works - or, indeed, what it does.
 * It seems to set up slideStuff[], an array of possible first-displayed
 * icons set against the matching y-positions of the slider.
 */
static void AdjustTop() {
	int tMissing, bMissing, nMissing;
	int nsliderYpos;
	int rowsWanted;
	int slideRange;
	int n, i;

	// Only do this if there's a slider
	if (!g_SlideObject)
		return;

	rowsWanted = (g_InvD[g_ino].NoofItems - g_InvD[g_ino].FirstDisp + g_InvD[g_ino].NoofHicons-1) / g_InvD[g_ino].NoofHicons;

	while (rowsWanted < g_InvD[g_ino].NoofVicons) {
		if (g_InvD[g_ino].FirstDisp) {
			g_InvD[g_ino].FirstDisp -= g_InvD[g_ino].NoofHicons;
			if (g_InvD[g_ino].FirstDisp < 0)
				g_InvD[g_ino].FirstDisp = 0;
			rowsWanted++;
		} else
			break;
	}
	tMissing = g_InvD[g_ino].FirstDisp ? (g_InvD[g_ino].FirstDisp + g_InvD[g_ino].NoofHicons-1)/g_InvD[g_ino].NoofHicons : 0;
	bMissing = (rowsWanted > g_InvD[g_ino].NoofVicons) ? rowsWanted - g_InvD[g_ino].NoofVicons : 0;

	nMissing = tMissing + bMissing;
	slideRange = g_sliderYmax - g_sliderYmin;

	if (!tMissing)
		nsliderYpos = g_sliderYmin;
	else if (!bMissing)
		nsliderYpos = g_sliderYmax;
	else {
		nsliderYpos = tMissing*slideRange/nMissing;
		nsliderYpos += g_sliderYmin;
	}

	if (nMissing) {
		n = g_InvD[g_ino].FirstDisp - tMissing*g_InvD[g_ino].NoofHicons;
		for (i = 0; i <= nMissing; i++, n += g_InvD[g_ino].NoofHicons) {
			g_slideStuff[i].n = n;
			g_slideStuff[i].y = (i*slideRange/nMissing) + g_sliderYmin;
		}
		if (g_slideStuff[0].n < 0)
			g_slideStuff[0].n = 0;
		assert(i < MAX_ININV + 1);
		g_slideStuff[i].n = -1;
	} else {
		g_slideStuff[0].n = 0;
		g_slideStuff[0].y = g_sliderYmin;
		g_slideStuff[1].n = -1;
	}

	if (nsliderYpos != g_sliderYpos) {
		MultiMoveRelXY(g_SlideObject, 0, nsliderYpos - g_sliderYpos);
		g_sliderYpos = nsliderYpos;
	}
}

/**
 * Insert an inventory icon object onto the display list.
 */
static OBJECT *AddInvObject(int num, const FREEL **pfreel, const FILM **pfilm) {
	INV_OBJECT *invObj;		// Icon data
	const MULTI_INIT *pmi;		// Its INIT structure - from the reel
	IMAGE *pim;		// ... you get the picture
	OBJECT *pPlayObj;	// The object we insert

	invObj = GetInvObject(num);

	// Get pointer to image
	pim = GetImageFromFilm(invObj->hIconFilm, 0, pfreel, &pmi, pfilm);

	// Poke in the background palette
	pim->hImgPal = TO_32(BgPal());

	// Set up the multi-object
	pPlayObj = MultiInitObject(pmi);
	MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj);

	return pPlayObj;
}

/**
 * Create display objects for the displayed icons in an inventory window.
 */
static void FillInInventory() {
	int	Index;		// Index into contents[]
	int	n = 0;		// index into iconArray[]
	int	xpos, ypos;
	int	row, col;
	const FREEL *pfr;
	const FILM *pfilm;

	DumpIconArray();

	if (g_InvDragging != ID_SLIDE)
		AdjustTop();		// Set up slideStuff[]

	Index = g_InvD[g_ino].FirstDisp;	// Start from first displayed object
	n = 0;
	ypos = START_ICONY;		// Y-offset of first display row

	for (row = 0; row < g_InvD[g_ino].NoofVicons; row++,	ypos += ITEM_HEIGHT + 1) {
		xpos = START_ICONX;		// X-offset of first display column

		for (col = 0; col < g_InvD[g_ino].NoofHicons; col++) {
			if (Index >= g_InvD[g_ino].NoofItems)
				break;
			else if (g_InvD[g_ino].contents[Index] != g_heldItem) {
				// Create a display object and position it
				g_iconArray[n] = AddInvObject(g_InvD[g_ino].contents[Index], &pfr, &pfilm);
				MultiSetAniXY(g_iconArray[n], g_InvD[g_ino].inventoryX + xpos , g_InvD[g_ino].inventoryY + ypos);
				MultiSetZPosition(g_iconArray[n], Z_INV_ICONS);

				InitStepAnimScript(&g_iconAnims[n], g_iconArray[n], FROM_32(pfr->script), ONE_SECOND / FROM_32(pfilm->frate));

				n++;
			}
			Index++;
			xpos += ITEM_WIDTH + 1;	// X-offset of next display column
		}
	}
}

enum {FROM_HANDLE, FROM_STRING};

/**
 * Set up a rectangle as the background to the inventory window.
 *  Additionally, sticks the window title up.
 */
static void AddBackground(OBJECT **rect, OBJECT **title, int extraH, int extraV, int textFrom) {
	// Why not 2 ????
	int width = g_TLwidth + extraH + g_TRwidth + NM_BG_SIZ_X;
	int height = g_TLheight + extraV + g_BLheight + NM_BG_SIZ_Y;

	// Create a rectangle object
	g_RectObject = *rect = TranslucentObject(width, height);

	// add it to display list and position it
	MultiInsertObject(GetPlayfieldList(FIELD_STATUS), *rect);
	MultiSetAniXY(*rect, g_InvD[g_ino].inventoryX + NM_BG_POS_X,
		g_InvD[g_ino].inventoryY + NM_BG_POS_Y);
	MultiSetZPosition(*rect, Z_INV_BRECT);

	if (title == NULL)
		return;

	// Create text object using title string
	if (textFrom == FROM_HANDLE) {
		LoadStringRes(g_InvD[g_ino].hInvTitle, TextBufferAddr(), TBUFSZ);
		*title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), 0,
					g_InvD[g_ino].inventoryX + width/2, g_InvD[g_ino].inventoryY + M_TOFF,
					GetTagFontHandle(), TXT_CENTER);
		assert(*title); // Inventory title string produced NULL text
		MultiSetZPosition(*title, Z_INV_HTEXT);
	} else if (textFrom == FROM_STRING && cd.ixHeading != NO_HEADING) {
		LoadStringRes(g_configStrings[cd.ixHeading], TextBufferAddr(), TBUFSZ);
		*title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), 0,
					g_InvD[g_ino].inventoryX + width/2, g_InvD[g_ino].inventoryY + M_TOFF,
					GetTagFontHandle(), TXT_CENTER);
		assert(*title); // Inventory title string produced NULL text
		MultiSetZPosition(*title, Z_INV_HTEXT);
	}
}

/**
 * Set up a rectangle as the background to the inventory window.
 */
static void AddBackground(OBJECT **rect, int extraH, int extraV) {
	AddBackground(rect, NULL, extraH, extraV, 0);
}

/**
 * Adds a title for a dialog
 */
static void AddTitle(POBJECT *title, int extraH) {
	int width = g_TLwidth + extraH + g_TRwidth + NM_BG_SIZ_X;

	// Create text object using title string
	if (g_InvD[g_ino].hInvTitle != (SCNHANDLE)NO_HEADING) {
		LoadStringRes(g_InvD[g_ino].hInvTitle, TextBufferAddr(), TBUFSZ);
		*title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), 0,
					g_InvD[g_ino].inventoryX + (width/2)+NM_BG_POS_X, g_InvD[g_ino].inventoryY + NM_TOFF,
					GetTagFontHandle(), TXT_CENTER, 0);
		assert(*title);
		MultiSetZPosition(*title, Z_INV_HTEXT);
	}
}


/**
 * Insert a part of the inventory window frame onto the display list.
 */
static OBJECT *AddObject(const FREEL *pfreel, int num) {
	const MULTI_INIT *pmi;	// Get the MULTI_INIT structure
	IMAGE *pim;
	OBJECT *pPlayObj;

	// Get pointer to image
	pim = GetImageFromReel(pfreel, &pmi);

	// Poke in the background palette
	pim->hImgPal = TO_32(BgPal());

	// Horrible bodge involving global variables to save
	// width and/or height of some window frame components
	if (num == g_TL) {
		g_TLwidth = FROM_16(pim->imgWidth);
		g_TLheight = FROM_16(pim->imgHeight) & ~C16_FLAG_MASK;
	} else if (num == g_TR) {
		g_TRwidth = FROM_16(pim->imgWidth);
	} else if (num == g_BL) {
		g_BLheight = FROM_16(pim->imgHeight) & ~C16_FLAG_MASK;
	}

	// Set up and insert the multi-object
	pPlayObj = MultiInitObject(pmi);
	MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj);

	return pPlayObj;
}

/**
 * Display the scroll bar slider.
 */

static void AddSlider(OBJECT **slide, const FILM *pfilm) {
	g_SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1);
	MultiSetAniXY(*slide, MultiRightmost(g_RectObject) + (TinselV2 ? NM_SLX : -M_SXOFF + 2),
		g_InvD[g_ino].inventoryY + g_sliderYpos);
	MultiSetZPosition(*slide, Z_INV_MFRAME);
}

/**
 * Display a box with some text in it.
 */
static void AddBox(int *pi, const int i) {
	int x	= g_InvD[g_ino].inventoryX + cd.box[i].xpos;
	int y	= g_InvD[g_ino].inventoryY + cd.box[i].ypos;
	int *pival = cd.box[i].ival;
	int	xdisp;
	const FILM *pFilm;

	switch (cd.box[i].boxType) {
	default:
		// Ignore if it's a blank scene hopper box
		if (TinselV2 && (cd.box[i].textMethod == TM_NONE))
			break;

		// Give us a box
		g_iconArray[*pi] = RectangleObject(BgPal(), TinselV2 ? BoxColor() : COL_BOX,
			cd.box[i].w, cd.box[i].h);
		MultiInsertObject(GetPlayfieldList(FIELD_STATUS), g_iconArray[*pi]);
		MultiSetAniXY(g_iconArray[*pi], x, y);
		MultiSetZPosition(g_iconArray[*pi], Z_INV_BRECT + 1);
		*pi += 1;

		// Stick in the text
		if ((cd.box[i].textMethod == TM_POINTER) ||
				(!TinselV2 && (cd.box[i].ixText == USE_POINTER))) {
			if (cd.box[i].boxText != NULL) {
				if (cd.box[i].boxType == RGROUP) {
					g_iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.box[i].boxText, 0,
#ifdef JAPAN
							x + 2, y+2, GetTagFontHandle(), 0);
#else
							x + 2, y + TYOFF, GetTagFontHandle(), 0);
#endif
				} else {
					g_iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.box[i].boxText, 0,
#ifdef JAPAN
// Note: it never seems to go here!
							x + cd.box[i].w/2, y+2, GetTagFontHandle(), TXT_CENTER);
#else
							x + cd.box[i].w / 2, y + TYOFF, GetTagFontHandle(), TXT_CENTER);
#endif
				}

				MultiSetZPosition(g_iconArray[*pi], Z_INV_ITEXT);
				*pi += 1;
			}
		} else {
			if (TinselV2) {
				if (cd.box[i].textMethod == TM_INDEX)
					LoadStringRes(SysString(cd.box[i].ixText), TextBufferAddr(), TBUFSZ);
				else {
					assert(cd.box[i].textMethod == TM_STRINGNUM);
					LoadStringRes(cd.box[i].ixText, TextBufferAddr(), TBUFSZ);
				}
			} else {
				LoadStringRes(g_configStrings[cd.box[i].ixText], TextBufferAddr(), TBUFSZ);
				assert(cd.box[i].boxType != RGROUP); // You'll need to add some code!
			}

			if (TinselV2 && (cd.box[i].boxType == RGROUP))
				g_iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), TextBufferAddr(),
						0, x + 2, y + TYOFF, GetTagFontHandle(), 0, 0);
			else
				g_iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS),
					TextBufferAddr(), 0,
#ifdef JAPAN
					x + cd.box[i].w/2, y+2, GetTagFontHandle(), TXT_CENTER);
#else
					x + cd.box[i].w / 2, y + TYOFF, GetTagFontHandle(), TXT_CENTER);
#endif
			MultiSetZPosition(g_iconArray[*pi], Z_INV_ITEXT);
			*pi += 1;
		}
		break;

	case AAGBUT:
	case ARSGBUT:
		pFilm = (const FILM *)LockMem(g_hWinParts);

		g_iconArray[*pi] = AddObject(&pFilm->reels[cd.box[i].bi + NORMGRAPH], -1);
		MultiSetAniXY(g_iconArray[*pi], x, y);
		MultiSetZPosition(g_iconArray[*pi], Z_INV_BRECT + 1);
		*pi += 1;

		break;

	case FRGROUP:
		assert(g_flagFilm != 0); // Language flags not declared!

		pFilm = (const FILM *)LockMem(g_flagFilm);

		if (_vm->_config->_isAmericanEnglishVersion && cd.box[i].bi == FIX_UK)
			cd.box[i].bi = FIX_USA;

		g_iconArray[*pi] = AddObject(&pFilm->reels[cd.box[i].bi], -1);
		MultiSetAniXY(g_iconArray[*pi], x, y);
		MultiSetZPosition(g_iconArray[*pi], Z_INV_BRECT+2);
		*pi += 1;

		break;

	case FLIP:
		pFilm = (const FILM *)LockMem(g_hWinParts);

		if (*pival)
			g_iconArray[*pi] = AddObject(&pFilm->reels[cd.box[i].bi], -1);
		else
			g_iconArray[*pi] = AddObject(&pFilm->reels[cd.box[i].bi+1], -1);
		MultiSetAniXY(g_iconArray[*pi], x, y);
		MultiSetZPosition(g_iconArray[*pi], Z_INV_BRECT+1);
		*pi += 1;

		// Stick in the text
		if (TinselV2) {
			assert(cd.box[i].textMethod == TM_INDEX);
			LoadStringRes(SysString(cd.box[i].ixText), TextBufferAddr(), TBUFSZ);
		} else {
			assert(cd.box[i].ixText != USE_POINTER);
			LoadStringRes(g_configStrings[cd.box[i].ixText], TextBufferAddr(), TBUFSZ);
		}
		g_iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS),
			TextBufferAddr(), 0, x + MDTEXT_XOFF, y + MDTEXT_YOFF, GetTagFontHandle(), TXT_RIGHT);
		MultiSetZPosition(g_iconArray[*pi], Z_INV_ITEXT);
		*pi += 1;
		break;

	case TOGGLE:
	case TOGGLE1:
	case TOGGLE2:
		pFilm = (const FILM *)LockMem(g_hWinParts);

		cd.box[i].bi = *pival ? IX_TICK1 : IX_CROSS1;
		g_iconArray[*pi] = AddObject(&pFilm->reels[cd.box[i].bi + NORMGRAPH], -1);
		MultiSetAniXY(g_iconArray[*pi], x, y);
		MultiSetZPosition(g_iconArray[*pi], Z_INV_BRECT+1);
		*pi += 1;

		// Stick in the text
		if (TinselV2) {
			assert(cd.box[i].textMethod == TM_INDEX);
			LoadStringRes(SysString(cd.box[i].ixText), TextBufferAddr(), TBUFSZ);
		} else {
			assert(cd.box[i].ixText != USE_POINTER);
			LoadStringRes(g_configStrings[cd.box[i].ixText], TextBufferAddr(), TBUFSZ);
		}

		if (cd.box[i].boxType == TOGGLE2) {
			g_iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS),
				TextBufferAddr(), 0, x + cd.box[i].w / 2, y + TOG2_YOFF,
				GetTagFontHandle(), TXT_CENTER, 0);
		} else {
			g_iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS),
				TextBufferAddr(), 0, x + MDTEXT_XOFF, y + MDTEXT_YOFF,
				GetTagFontHandle(), TXT_RIGHT, 0);
		}

		MultiSetZPosition(g_iconArray[*pi], Z_INV_ITEXT);
		*pi += 1;
		break;

	case SLIDER:
		pFilm = (const FILM *)LockMem(g_hWinParts);
		xdisp = SLIDE_RANGE*(*pival)/cd.box[i].w;

		g_iconArray[*pi] = AddObject(&pFilm->reels[IX_MDGROOVE], -1);
		MultiSetAniXY(g_iconArray[*pi], x, y);
		MultiSetZPosition(g_iconArray[*pi], Z_MDGROOVE);
		*pi += 1;
		g_iconArray[*pi] = AddObject(&pFilm->reels[IX_MDSLIDER], -1);
		MultiSetAniXY(g_iconArray[*pi], x+SLIDE_MINX+xdisp, y);
		MultiSetZPosition(g_iconArray[*pi], Z_MDSLIDER);
		assert(g_numMdSlides < MAXSLIDES);
		g_mdSlides[g_numMdSlides].num = i;
		g_mdSlides[g_numMdSlides].min = x + SLIDE_MINX;
		g_mdSlides[g_numMdSlides].max = x + SLIDE_MAXX;
		g_mdSlides[g_numMdSlides++].obj = g_iconArray[*pi];
		*pi += 1;

		// Stick in the text
		if (TinselV2) {
			assert(cd.box[i].textMethod == TM_INDEX);
			LoadStringRes(SysString(cd.box[i].ixText), TextBufferAddr(), TBUFSZ);
		} else {
			assert(cd.box[i].ixText != USE_POINTER);
			LoadStringRes(g_configStrings[cd.box[i].ixText], TextBufferAddr(), TBUFSZ);
		}
		g_iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS),
			TextBufferAddr(), 0, x+MDTEXT_XOFF, y+MDTEXT_YOFF, GetTagFontHandle(), TXT_RIGHT);
		MultiSetZPosition(g_iconArray[*pi], Z_INV_ITEXT);
		*pi += 1;
		break;

	case ROTATE:
		pFilm = (const FILM *)LockMem(g_hWinParts);

		// Left one
		if (!g_bNoLanguage) {
			g_iconArray[*pi] = AddObject(&pFilm->reels[IX2_LEFT1], -1);
			MultiSetAniXY(g_iconArray[*pi], x-ROTX1, y);
			MultiSetZPosition(g_iconArray[*pi], Z_INV_BRECT + 1);
			*pi += 1;

			// Right one
			g_iconArray[*pi] = AddObject( &pFilm->reels[IX2_RIGHT1], -1);
			MultiSetAniXY(g_iconArray[*pi], x + ROTX1, y);
			MultiSetZPosition(g_iconArray[*pi], Z_INV_BRECT + 1);
			*pi += 1;

			// Stick in the text
			assert(cd.box[i].textMethod == TM_INDEX);
			LoadStringRes(SysString(cd.box[i].ixText), TextBufferAddr(), TBUFSZ);
			g_iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS),
				TextBufferAddr(), 0, x + cd.box[i].w / 2, y + TOG2_YOFF,
				GetTagFontHandle(), TXT_CENTER, 0);
			MultiSetZPosition(g_iconArray[*pi], Z_INV_ITEXT);
			*pi += 1;
		}

		// Current language's text
		if (LanguageDesc(g_displayedLanguage) == 0)
			break;

		LoadStringRes(LanguageDesc(g_displayedLanguage), TextBufferAddr(), TBUFSZ);
		g_iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), TextBufferAddr(), 0,
				x + cd.box[i].w / 2, y + ROT_YOFF, GetTagFontHandle(), TXT_CENTER, 0);
		MultiSetZPosition(g_iconArray[*pi], Z_INV_ITEXT);
		*pi += 1;

		// Current language's flag
		pFilm = (const FILM *)LockMem(LanguageFlag(g_displayedLanguage));
		g_iconArray[*pi] = AddObject(&pFilm->reels[0], -1);
		MultiSetAniXY(g_iconArray[*pi], x + FLAGX, y + FLAGY);
		MultiSetZPosition(g_iconArray[*pi], Z_INV_BRECT + 1);
		*pi += 1;
		break;
	}
}

/**
 * Display some boxes.
 */
static void AddBoxes(bool bPosnSlide) {
	int	objCount = NUMHL;	// Object count - allow for HL1, HL2 etc.

	DumpIconArray();
	g_numMdSlides = 0;

	for (int i = 0; i < cd.NumBoxes; i++) {
		AddBox(&objCount, i);
	}

	if (cd.bExtraWin) {
		if (bPosnSlide && !TinselV2)
			g_sliderYpos = g_sliderYmin + (cd.extraBase*(g_sliderYmax-g_sliderYmin))/(MAX_SAVED_FILES-NUM_RGROUP_BOXES);
		else if (bPosnSlide) {
			// Tinsel 2 bPosnSlide code
			int lastY = g_sliderYpos;

			if (cd.box == loadBox || cd.box == saveBox)
				g_sliderYpos = g_sliderYmin + (cd.extraBase * (sliderRange)) /
				(MAX_SAVED_FILES - NUM_RGROUP_BOXES);
			else if (cd.box == hopperBox1) {
				if (g_numScenes <= NUM_RGROUP_BOXES)
					g_sliderYpos = g_sliderYmin;
				else
					g_sliderYpos = g_sliderYmin + (cd.extraBase*(sliderRange))/(g_numScenes-NUM_RGROUP_BOXES);
			} else if (cd.box == hopperBox2) {
				if (g_numEntries <= NUM_RGROUP_BOXES)
					g_sliderYpos = g_sliderYmin;
				else
					g_sliderYpos = g_sliderYmin + (cd.extraBase * (sliderRange)) /
					(g_numEntries-NUM_RGROUP_BOXES);
			}

			MultiMoveRelXY(g_SlideObject, 0, g_sliderYpos - lastY);
		}

		if (!TinselV2)
			MultiSetAniXY(g_SlideObject, g_InvD[g_ino].inventoryX + 24 + 179, g_sliderYpos);
	}

	assert(objCount < MAX_ICONS); // added too many icons
}

/**
 * Display the scroll bar slider.
 */
static void AddEWSlider(OBJECT **slide, const FILM *pfilm) {
	g_SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1);
	MultiSetAniXY(*slide, g_InvD[g_ino].inventoryX + 24 + 127, g_sliderYpos);
	MultiSetZPosition(*slide, Z_INV_MFRAME);
}

/**
 * AddExtraWindow
 */
static int AddExtraWindow(int x, int y, OBJECT **retObj) {
	int	n = 0;
	const FILM *pfilm;

	// Get the frame's data
	pfilm = (const FILM *)LockMem(g_hWinParts);

	x += TinselV2 ? 30 : 20;
	y += TinselV2 ? 38 : 24;

	// Draw the four corners
	retObj[n] = AddObject(&pfilm->reels[IX_RTL], -1);	// Top left
	MultiSetAniXY(retObj[n], x, y);
	MultiSetZPosition(retObj[n], Z_INV_MFRAME);
	n++;
	retObj[n] = AddObject(&pfilm->reels[IX_NTR], -1);	// Top right
	MultiSetAniXY(retObj[n], x + (TinselV2 ? g_TLwidth + 312 : 152), y);
	MultiSetZPosition(retObj[n], Z_INV_MFRAME);
	n++;
	retObj[n] = AddObject(&pfilm->reels[IX_BL], -1);	// Bottom left
	MultiSetAniXY(retObj[n], x, y + (TinselV2 ? g_TLheight + 208 : 124));
	MultiSetZPosition(retObj[n], Z_INV_MFRAME);
	n++;
	retObj[n] = AddObject(&pfilm->reels[IX_BR], -1);	// Bottom right
	MultiSetAniXY(retObj[n], x + (TinselV2 ? g_TLwidth + 312 : 152),
		y + (TinselV2 ? g_TLheight + 208 : 124));
	MultiSetZPosition(retObj[n], Z_INV_MFRAME);
	n++;

	// Draw the edges
	retObj[n] = AddObject(&pfilm->reels[IX_H156], -1);	// Top
	MultiSetAniXY(retObj[n], x + (TinselV2 ? g_TLwidth : 6), y + NM_TBT);
	MultiSetZPosition(retObj[n], Z_INV_MFRAME);
	n++;
	retObj[n] = AddObject(&pfilm->reels[IX_H156], -1);	// Bottom
	MultiSetAniXY(retObj[n], x + (TinselV2 ? g_TLwidth : 6), y +
		(TinselV2 ? g_TLheight + 208 + g_BLheight + NM_BSY : 143));
	MultiSetZPosition(retObj[n], Z_INV_MFRAME);
	n++;
	retObj[n] = AddObject(&pfilm->reels[IX_V104], -1);	// Left
	MultiSetAniXY(retObj[n], x + NM_LSX, y + (TinselV2 ? g_TLheight : 20));
	MultiSetZPosition(retObj[n], Z_INV_MFRAME);
	n++;
	retObj[n] = AddObject(&pfilm->reels[IX_V104], -1);	// Right 1
	MultiSetAniXY(retObj[n], x + (TinselV2 ? g_TLwidth + 312 + g_TRwidth + NM_RSX : 179),
		y + (TinselV2 ? g_TLheight : 20));
	MultiSetZPosition(retObj[n], Z_INV_MFRAME);
	n++;
	retObj[n] = AddObject(&pfilm->reels[IX_V104], -1);	// Right 2
	MultiSetAniXY(retObj[n], x + (TinselV2 ? g_TLwidth + 312 + g_TRwidth + NM_SBL : 188),
		y + (TinselV2 ? g_TLheight : 20));
	MultiSetZPosition(retObj[n], Z_INV_MFRAME);
	n++;

	if (TinselV2) {
		g_sliderYpos = g_sliderYmin = y + 27;
		g_sliderYmax = y + 273;

		retObj[n++] = g_SlideObject = AddObject( &pfilm->reels[IX_SLIDE], -1);
		MultiSetAniXY(g_SlideObject,
			x + g_TLwidth + 320 + g_TRwidth - NM_BG_POS_X + NM_BG_SIZ_X - 2,
			g_sliderYpos);
		MultiSetZPosition(g_SlideObject, Z_INV_MFRAME);
	} else {
		g_sliderYpos = g_sliderYmin = y + 9;
		g_sliderYmax = y + 134;
		AddEWSlider(&retObj[n++], pfilm);
	}

	return n;
}


enum InventoryType { EMPTY, FULL, CONF };

/**
 * Construct an inventory window - either a standard one, with
 * background, slider and icons, or a re-sizing window.
 */
static void ConstructInventory(InventoryType filling) {
	int	eH, eV;		// Extra width and height
	int	n = 0;		// Index into object array
	int	zpos;		// Z-position of frame
	int	invX = g_InvD[g_ino].inventoryX;
	int	invY = g_InvD[g_ino].inventoryY;
	OBJECT **retObj;
	const FILM *pfilm;

	// Select the object array to use
	if (filling == FULL || filling == CONF) {
		retObj = g_objArray;		// Standard window
		zpos = Z_INV_MFRAME;
	} else {
		retObj = g_DobjArray;		// Re-sizing window
		zpos = Z_INV_RFRAME;
	}

	// Dispose of anything it may be replacing
	for (int i = 0; i < MAX_WCOMP; i++) {
		if (retObj[i] != NULL) {
			MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), retObj[i]);
			retObj[i] = NULL;
		}
	}

	// Get the frame's data
	pfilm = (const FILM *)LockMem(g_hWinParts);

	// Standard window is of granular dimensions
	if (filling == FULL) {
		// Round-up/down to nearest number of icons
		if (g_SuppH > ITEM_WIDTH / 2)
			g_InvD[g_ino].NoofHicons++;
		if (g_SuppV > ITEM_HEIGHT / 2)
			g_InvD[g_ino].NoofVicons++;
		g_SuppH = g_SuppV = 0;
	}

	// Extra width and height
	eH = (g_InvD[g_ino].NoofHicons - 1) * (ITEM_WIDTH+I_SEPARATION) + g_SuppH;
	eV = (g_InvD[g_ino].NoofVicons - 1) * (ITEM_HEIGHT+I_SEPARATION) + g_SuppV;

	// Which window frame corners to use
	if (TinselV2 && (g_ino == INV_CONV)) {
		g_TL = IX_TL;
		g_TR = IX2_TR4;
		g_BL = IX_BL;
		g_BR = IX_RBR;
	} else if ((filling == FULL) && (g_ino != INV_CONV)) {
		g_TL = IX_TL;
		g_TR = IX_TR;
		g_BL = IX_BL;
		g_BR = IX_BR;
	} else {
		g_TL = IX_RTL;
		g_TR = IX_RTR;
		g_BL = IX_BL;
		g_BR = IX_RBR;
	}

	// Draw the four corners
	retObj[n] = AddObject(&pfilm->reels[g_TL], g_TL);
	MultiSetAniXY(retObj[n], invX, invY);
	MultiSetZPosition(retObj[n], zpos);
	n++;
	retObj[n] = AddObject(&pfilm->reels[g_TR], g_TR);
	MultiSetAniXY(retObj[n], invX + g_TLwidth + eH, invY);
	MultiSetZPosition(retObj[n], zpos);
	n++;
	retObj[n] = AddObject(&pfilm->reels[g_BL], g_BL);
	MultiSetAniXY(retObj[n], invX, invY + g_TLheight + eV);
	MultiSetZPosition(retObj[n], zpos);
	n++;
	retObj[n] = AddObject(&pfilm->reels[g_BR], g_BR);
	MultiSetAniXY(retObj[n], invX + g_TLwidth + eH, invY + g_TLheight + eV);
	MultiSetZPosition(retObj[n], zpos);
	n++;

	// Draw extra Top and bottom parts
	if (g_InvD[g_ino].NoofHicons > 1) {
		// Top side
		retObj[n] = AddObject(&pfilm->reels[hFillers[g_InvD[g_ino].NoofHicons-2]], -1);
		MultiSetAniXY(retObj[n], invX + g_TLwidth, invY + NM_TBT);
		MultiSetZPosition(retObj[n], zpos);
		n++;

		// Bottom of header box
		if (filling == FULL) {
			if (TinselV2) {
				retObj[n] = AddObject(&pfilm->reels[hFillers[g_InvD[g_ino].NoofHicons-2]], -1);
				MultiSetAniXY(retObj[n], invX + g_TLwidth, invY + NM_TBB);
				MultiSetZPosition(retObj[n], zpos);
				n++;
			} else {
				retObj[n] = AddObject(&pfilm->reels[hFillers[g_InvD[g_ino].NoofHicons-2]], -1);
				MultiSetAniXY(retObj[n], invX + g_TLwidth, invY + M_TBB + 1);
				MultiSetZPosition(retObj[n], zpos);
				n++;

				// Extra bits for conversation - hopefully temporary
				if (g_ino == INV_CONV) {
					retObj[n] = AddObject(&pfilm->reels[IX_H26], -1);
					MultiSetAniXY(retObj[n], invX + g_TLwidth - 2, invY + M_TBB + 1);
					MultiSetZPosition(retObj[n], zpos);
					n++;

					retObj[n] = AddObject(&pfilm->reels[IX_H52], -1);
					MultiSetAniXY(retObj[n], invX + eH - 10, invY + M_TBB + 1);
					MultiSetZPosition(retObj[n], zpos);
					n++;
				}
			}
		}

		// Bottom side
		retObj[n] = AddObject(&pfilm->reels[hFillers[g_InvD[g_ino].NoofHicons-2]], -1);
		MultiSetAniXY(retObj[n], invX + g_TLwidth, invY + g_TLheight + eV + g_BLheight + NM_BSY);

		MultiSetZPosition(retObj[n], zpos);
		n++;
	}
	if (g_SuppH) {
		int offx = g_TLwidth + eH - (TinselV2 ? ITEM_WIDTH + I_SEPARATION : 26);
		if (offx < g_TLwidth)	// Not too far!
			offx = g_TLwidth;

		// Top side extra
		retObj[n] = AddObject(&pfilm->reels[IX_H26], -1);
		MultiSetAniXY(retObj[n], invX + offx, invY + NM_TBT);
		MultiSetZPosition(retObj[n], zpos);
		n++;

		// Bottom side extra
		retObj[n] = AddObject(&pfilm->reels[IX_H26], -1);
		MultiSetAniXY(retObj[n], invX + offx, invY + g_TLheight + eV + g_BLheight + NM_BSY);

		MultiSetZPosition(retObj[n], zpos);
		n++;
	}

	// Draw extra side parts
	if (g_InvD[g_ino].NoofVicons > 1) {
		// Left side
		retObj[n] = AddObject(&pfilm->reels[vFillers[g_InvD[g_ino].NoofVicons-2]], -1);
		MultiSetAniXY(retObj[n], invX + NM_LSX, invY + g_TLheight);
		MultiSetZPosition(retObj[n], zpos);
		n++;

		// Left side of scroll bar
		if (filling == FULL && g_ino != INV_CONV) {
			retObj[n] = AddObject(&pfilm->reels[vFillers[g_InvD[g_ino].NoofVicons-2]], -1);
			if (TinselV2)
				MultiSetAniXY(retObj[n], invX + g_TLwidth + eH + g_TRwidth + NM_SBL, invY + g_TLheight);
			else
				MultiSetAniXY(retObj[n], invX + g_TLwidth + eH + M_SBL + 1, invY + g_TLheight);
			MultiSetZPosition(retObj[n], zpos);
			n++;
		}

		// Right side
		retObj[n] = AddObject(&pfilm->reels[vFillers[g_InvD[g_ino].NoofVicons-2]], -1);
		MultiSetAniXY(retObj[n], invX + g_TLwidth + eH + g_TRwidth + NM_RSX, invY + g_TLheight);
		MultiSetZPosition(retObj[n], zpos);
		n++;
	}
	if (g_SuppV) {
		int offy = g_TLheight + eV - (TinselV2 ? ITEM_HEIGHT + I_SEPARATION : 26);
		int minAmount = TinselV2 ? 20 : 5;
		if (offy < minAmount)
			offy = minAmount;

		// Left side extra
		retObj[n] = AddObject(&pfilm->reels[IX_V26], -1);
		MultiSetAniXY(retObj[n], invX + NM_LSX, invY + offy);
		MultiSetZPosition(retObj[n], zpos);
		n++;

		// Right side extra
		retObj[n] = AddObject(&pfilm->reels[IX_V26], -1);
		MultiSetAniXY(retObj[n], invX + g_TLwidth + eH + g_TRwidth + NM_RSX, invY + offy);
		MultiSetZPosition(retObj[n], zpos);
		n++;
	}

	OBJECT **rect, **title;

	// Draw background, slider and icons
	if (TinselV2 && (filling != EMPTY)) {
		AddBackground(&retObj[n++], eH, eV);
		AddTitle(&retObj[n++], eH);
	}

	if (filling == FULL) {
		if (!TinselV2) {
			rect = &retObj[n++];
			title = &retObj[n++];

			AddBackground(rect, title, eH, eV, FROM_HANDLE);
		}

		if (g_ino == INV_CONV) {
			g_SlideObject = NULL;

			if (TinselV2) {
				// !!!!! MAGIC NUMBER ALERT !!!!!
				// Make sure it's big enough for the heading
				if (MultiLeftmost(retObj[n-1]) < g_InvD[INV_CONV].inventoryX + 10) {
					g_InvD[INV_CONV].NoofHicons++;
					ConstructInventory(FULL);
				}
			}
		} else if (g_InvD[g_ino].NoofItems > g_InvD[g_ino].NoofHicons*g_InvD[g_ino].NoofVicons) {
			g_sliderYmin = g_TLheight - (TinselV2 ? 1 : 2);
			g_sliderYmax = g_TLheight + eV + (TinselV2 ? 12 : 10);
			AddSlider(&retObj[n++], pfilm);
		}

		FillInInventory();
	} else if (filling == CONF) {
		if (!TinselV2) {
			rect = &retObj[n++];
			title = &retObj[n++];

			AddBackground(rect, title, eH, eV, FROM_STRING);
			if (cd.bExtraWin)
				n += AddExtraWindow(invX, invY, &retObj[n]);
		} else {
			if (cd.bExtraWin)
				AddExtraWindow(invX, invY, &retObj[n]);
		}

		AddBoxes(true);
	}

	assert(n < MAX_WCOMP); // added more parts than we can handle!

	// Reposition returns true if needs to move
	if (g_InvD[g_ino].bMoveable && filling == FULL && RePosition()) {
		ConstructInventory(FULL);
	}
}


/**
 * Call this when drawing a 'FULL', movable inventory. Checks that the
 * position of the Translucent object is within limits. If it isn't,
 * adjusts the x/y position of the current inventory and returns true.
 */
static bool RePosition() {
	int	p;
	bool	bMoveitMoveit = false;

	assert(g_RectObject); // no recangle object!

	// Test for off-screen horizontally
	p = MultiLeftmost(g_RectObject);
	if (p > MAXLEFT) {
		// Too far to the right
		g_InvD[g_ino].inventoryX += MAXLEFT - p;
		bMoveitMoveit = true;			// I like to....
	} else {
		// Too far to the left?
		p = MultiRightmost(g_RectObject);
		if (p < MINRIGHT) {
			g_InvD[g_ino].inventoryX += MINRIGHT - p;
			bMoveitMoveit = true;		// I like to....
		}
	}

	// Test for off-screen vertically
	p = MultiHighest(g_RectObject);
	if (p < MINTOP) {
		// Too high
		g_InvD[g_ino].inventoryY += MINTOP - p;
		bMoveitMoveit = true;			// I like to....
	} else if (p > MAXTOP) {
		// Too low
		g_InvD[g_ino].inventoryY += MAXTOP - p;
		bMoveitMoveit = true;			// I like to....
	}

	return bMoveitMoveit;
}

/**************************************************************************/
/***/
/**************************************************************************/

/**
 * Get the cursor's reel, poke in the background palette,
 * and customise the cursor.
 */
static void AlterCursor(int num) {
	const FREEL *pfreel;
	IMAGE *pim;

	// Get pointer to image
	pim = GetImageFromFilm(g_hWinParts, num, &pfreel);

	// Poke in the background palette
	pim->hImgPal = TO_32(BgPal());

	SetTempCursor(FROM_32(pfreel->script));
}

enum InvCursorFN {IC_AREA, IC_DROP};

/**
 * InvCursor
 */
static void InvCursor(InvCursorFN fn, int CurX, int CurY) {
	static enum { IC_NORMAL, IC_DR, IC_UR, IC_TB, IC_LR,
		IC_INV, IC_UP, IC_DN } ICursor = IC_NORMAL;	// FIXME: Avoid non-const global vars

	int	area;		// The part of the window the cursor is over
	bool	restoreMain = false;

	// If currently dragging, don't be messing about with the cursor shape
	if (g_InvDragging != ID_NONE)
		return;

	switch (fn) {
	case IC_DROP:
		ICursor = IC_NORMAL;
		InvCursor(IC_AREA, CurX, CurY);
		break;

	case IC_AREA:
		area = InvArea(CurX, CurY);

		// Check for POINTED events
		if (g_ino == INV_CONF)
			InvBoxes(area == I_BODY, CurX, CurY);
		else
			InvLabels(area == I_BODY, CurX, CurY);

		// No cursor trails while within inventory window
		if (area == I_NOTIN)
			UnHideCursorTrails();
		else
			HideCursorTrails();

		switch (area) {
		case I_NOTIN:
			restoreMain = true;
			break;

		case I_TLEFT:
		case I_BRIGHT:
			if (!g_InvD[g_ino].resizable)
				restoreMain = true;
			else if (ICursor != IC_DR) {
				AlterCursor(IX_CURDD);
				ICursor = IC_DR;
			}
			break;

		case I_TRIGHT:
		case I_BLEFT:
			if (!g_InvD[g_ino].resizable)
				restoreMain = true;
			else if (ICursor != IC_UR) {
				AlterCursor(IX_CURDU);
				ICursor = IC_UR;
			}
			break;

		case I_TOP:
		case I_BOTTOM:
			if (!g_InvD[g_ino].resizable) {
				restoreMain = true;
				break;
			}
			if (ICursor != IC_TB) {
				AlterCursor(IX_CURUD);
				ICursor = IC_TB;
			}
			break;

		case I_LEFT:
		case I_RIGHT:
			if (!g_InvD[g_ino].resizable)
				restoreMain = true;
			else if (ICursor != IC_LR) {
				AlterCursor(IX_CURLR);
				ICursor = IC_LR;
			}
			break;

		case I_UP:
		case I_SLIDE_UP:
		case I_DOWN:
		case I_SLIDE_DOWN:
		case I_SLIDE:
		case I_HEADER:
		case I_BODY:
			restoreMain = true;
			break;
		}
		break;
	}

	if (restoreMain && ICursor != IC_NORMAL) {
		RestoreMainCursor();
		ICursor = IC_NORMAL;
	}
}




/*-------------------------------------------------------------------------*/


/**************************************************************************/
/******************** Conversation specific functions *********************/
/**************************************************************************/


extern void ConvAction(int index) {
	assert(g_ino == INV_CONV); // not conv. window!
	PMOVER pMover = TinselV2 ? GetMover(GetLeadId()) : NULL;

	switch (index) {
	case INV_NOICON:
		return;

	case INV_CLOSEICON:
		g_thisIcon = -1;	// Postamble
		break;

	case INV_OPENICON:
		// Store the direction the lead character is facing in when the conversation starts
		if (TinselV2)
			g_initialDirection = GetMoverDirection(pMover);
		g_thisIcon = -2;	// Preamble
		break;

	default:
		g_thisIcon = g_InvD[g_ino].contents[index];
		break;
	}

	if (!TinselV2)
		RunPolyTinselCode(g_thisConvPoly, CONVERSE, PLR_NOEVENT, true);
	else {
		// If the lead's direction has changed for any reason (such as having broken the
		// fourth wall and talked to the screen), reset back to the original direction
		DIRECTION currDirection = GetMoverDirection(pMover);
		if (currDirection != g_initialDirection) {
			SetMoverDirection(pMover, g_initialDirection);
			SetMoverStanding(pMover);
		}

		if (g_thisConvPoly != NOPOLY)
			PolygonEvent(Common::nullContext, g_thisConvPoly, CONVERSE, 0, false, 0);
		else
			ActorEvent(Common::nullContext, g_thisConvActor, CONVERSE, false, 0);
	}

}

/**
 * Called to specify whether conversation window is going to
 * appear at the top or bottom of the screen.
 * Also to specify which polygon or actor is opening the conversation.
 *
 * Note: ano may (will probably) be set when it's a polygon.
 */
extern void SetConvDetails(CONV_PARAM fn, HPOLYGON hPoly, int ano) {
	g_thisConvFn = fn;
	g_thisConvPoly = hPoly;
	g_thisConvActor = ano;

	g_bMoveOnUnHide = true;

	// Get the Actor Tag's or Tagged Actor's label for the conversation window title
	if (hPoly != NOPOLY)	{
		int x, y;
		GetTagTag(hPoly, &g_InvD[INV_CONV].hInvTitle, &x, &y);
	} else {
		g_InvD[INV_CONV].hInvTitle = GetActorTagHandle(ano);
	}
}

/*-------------------------------------------------------------------------*/

/**
 * Add an icon to the permanent conversation list.
 */
extern void PermaConvIcon(int icon, bool bEnd) {
	int i;

	// See if it's already there
	for (i = 0; i < g_numPermIcons; i++) {
		if (g_permIcons[i] == icon)
			break;
	}

	// Add it if it isn't already there
	if (i == g_numPermIcons) {
		assert(g_numPermIcons < MAX_PERMICONS);

		if (bEnd || !g_numEndIcons) {
			// Add it at the end
			g_permIcons[g_numPermIcons++] = icon;
			if (bEnd)
				g_numEndIcons++;
		} else {
			// Insert before end icons
			memmove(&g_permIcons[g_numPermIcons-g_numEndIcons+1],
				&g_permIcons[g_numPermIcons-g_numEndIcons],
				g_numEndIcons * sizeof(int));
			g_permIcons[g_numPermIcons-g_numEndIcons] = icon;
			g_numPermIcons++;
		}
	}
}

/*-------------------------------------------------------------------------*/

extern void convPos(int fn) {
	if (fn == CONV_DEF)
		g_InvD[INV_CONV].inventoryY = 8;
	else if (fn == CONV_BOTTOM)
		g_InvD[INV_CONV].inventoryY = 150;
}

extern void ConvPoly(HPOLYGON hPoly) {
	g_thisConvPoly = hPoly;
}

extern int GetIcon() {
	return g_thisIcon;
}

extern void CloseDownConv() {
	if (g_InventoryState == ACTIVE_INV && g_ino == INV_CONV) {
		KillInventory();
	}
}

extern void HideConversation(bool bHide) {
	int aniX, aniY;
	int i;

	if (g_InventoryState == ACTIVE_INV && g_ino == INV_CONV) {
		if (bHide) {
			// Move all the window and icons off-screen
			for (i = 0; i < MAX_WCOMP && g_objArray[i]; i++) {
				MultiAdjustXY(g_objArray[i], 2 * SCREEN_WIDTH, 0);
			}
			for (i = 0; i < MAX_ICONS && g_iconArray[i]; i++) {
				MultiAdjustXY(g_iconArray[i], 2 * SCREEN_WIDTH, 0);
			}

			// Window is hidden
			g_InventoryHidden = true;

			// Remove any labels
			InvLabels(false, 0, 0);
		} else {
			// Window is not hidden
			g_InventoryHidden = false;

			if (TinselV2 && g_ItemsChanged)
				// Just rebuild the whole thing
				ConstructInventory(FULL);
			else {
				// Move it all back on-screen
				for (i = 0; i < MAX_WCOMP && g_objArray[i]; i++) {
					MultiAdjustXY(g_objArray[i], -2 * SCREEN_WIDTH, 0);
				}

				// Don't flash if items changed. If they have, will be redrawn anyway.
				if (TinselV2 || !g_ItemsChanged) {
					for (i = 0; i < MAX_ICONS && g_iconArray[i]; i++) {
						MultiAdjustXY(g_iconArray[i], -2*SCREEN_WIDTH, 0);
					}
				}
			}

			if (TinselV2 && g_bMoveOnUnHide) {
				/*
				 * First time, position it appropriately
				 */
				int left, center;
				int x, y, deltay;

				// Only do it once per conversation
				g_bMoveOnUnHide = false;

				// Current center of the window
				left = MultiLeftmost(g_RectObject);
				center = (MultiRightmost(g_RectObject) + left) / 2;

				// Get the x-offset for the conversation window
				if (g_thisConvActor) {
					int Loffset, Toffset;

					GetActorMidTop(g_thisConvActor, &x, &y);
					PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
					x -= Loffset;
					y -= Toffset;
				} else {
					x = SCREEN_WIDTH / 2;
					y = SCREEN_BOX_HEIGHT2 / 2;
				}

				// Save old y-position
				deltay = g_InvD[INV_CONV].inventoryY;

				switch (g_thisConvFn) {
				case CONV_TOP:
					g_InvD[INV_CONV].inventoryY = SysVar(SV_CONV_TOPY);
					break;

				case CONV_BOTTOM:
					g_InvD[INV_CONV].inventoryY = SysVar(SV_CONV_BOTY);
					break;

				case CONV_DEF:
					g_InvD[INV_CONV].inventoryY = y - SysVar(SV_CONV_ABOVE_Y);
					break;

				default:
					break;
				}

				// Calculate y change
				deltay = g_InvD[INV_CONV].inventoryY - deltay;

				// Move it all
				for (i = 0; i < MAX_WCOMP && g_objArray[i]; i++) {
					MultiMoveRelXY(g_objArray[i], x - center, deltay);
				}
				for (i = 0; i < MAX_ICONS && g_iconArray[i]; i++) {
					MultiMoveRelXY(g_iconArray[i], x - center, deltay);
				}
				g_InvD[INV_CONV].inventoryX += x - center;

				/*
				 * Now positioned as worked out
				 * - but it must be in a sensible place
				*/
				if (MultiLeftmost(g_RectObject) < SysVar(SV_CONV_MINX))
					x = SysVar(SV_CONV_MINX) - MultiLeftmost(g_RectObject);
				else if (MultiRightmost(g_RectObject) > SCREEN_WIDTH - SysVar(SV_CONV_MINX))
					x = SCREEN_WIDTH - SysVar(SV_CONV_MINX) - MultiRightmost(g_RectObject);
				else
					x = 0;

				if (g_thisConvFn == CONV_DEF && MultiHighest(g_RectObject) < SysVar(SV_CONV_MINY)
						&& g_thisConvActor) {
					int Loffset, Toffset;

					PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
					y = GetActorBottom(g_thisConvActor) - MultiHighest(g_RectObject) +
						SysVar(SV_CONV_BELOW_Y);
					y -= Toffset;
				}
				else
					y = 0;

				if (x || y) {
					for (i = 0; i < MAX_WCOMP && g_objArray[i]; i++) {
						MultiMoveRelXY(g_objArray[i], x, y);
					}
					for (i = 0; i < MAX_ICONS && g_iconArray[i]; i++) {
						MultiMoveRelXY(g_iconArray[i], x, y);
					}
					g_InvD[INV_CONV].inventoryX += x;
					g_InvD[INV_CONV].inventoryY += y;
				}

				/*
				 * Oh shit! We might have gone off the bottom
				 */
				if (MultiLowest(g_RectObject) > SCREEN_BOX_HEIGHT2 - SysVar(SV_CONV_MINY)) {
					y = (SCREEN_BOX_HEIGHT2 - SysVar(SV_CONV_MINY)) - MultiLowest(g_RectObject);
					for (i = 0; i < MAX_WCOMP && g_objArray[i]; i++) {
						MultiMoveRelXY(g_objArray[i], 0, y);
					}
					for (i = 0; i < MAX_ICONS && g_iconArray[i]; i++) {
						MultiMoveRelXY(g_iconArray[i], 0, y);
					}
					g_InvD[INV_CONV].inventoryY += y;
				}
			}

			GetCursorXY(&aniX, &aniY, false);
			InvLabels(true, aniX, aniY);
		}
	}
}

extern bool ConvIsHidden() {
	return g_InventoryHidden;
}


/**************************************************************************/
/******************* Open and closing functions ***************************/
/**************************************************************************/

/**
 * Start up an inventory window.
 */
extern void PopUpInventory(int invno) {
	assert(invno == INV_1 || invno == INV_2 || invno == INV_CONV
		|| invno == INV_CONF || invno == INV_MENU); // Trying to open illegal inventory

	if (g_InventoryState == IDLE_INV) {
		g_bReOpenMenu = false;	// Better safe than sorry...

		DisableTags();		// Tags disabled during inventory
		if (TinselV2)
			DisablePointing();	// Pointing disabled during inventory

		if (invno == INV_CONV) {	// Conversation window?
			if (TinselV2)
				// Quiet please..
				_vm->_pcmMusic->dim(false);

			// Start conversation with permanent contents
			memset(g_InvD[INV_CONV].contents, 0, MAX_ININV*sizeof(int));
			memcpy(g_InvD[INV_CONV].contents, g_permIcons, g_numPermIcons*sizeof(int));
			g_InvD[INV_CONV].NoofItems = g_numPermIcons;
			if (TinselV2)
				g_InvD[INV_CONV].NoofHicons = g_numPermIcons;
			else
				g_thisIcon = 0;
		} else if (invno == INV_CONF) {	// Configuration window?
			cd.selBox = NOBOX;
			cd.pointBox = NOBOX;
		}

		g_ino = invno;			// The open inventory

		g_ItemsChanged = false;		// Nothing changed
		g_InvDragging = ID_NONE;		// Not dragging
		g_InventoryState = ACTIVE_INV;	// Inventory actiive
		g_InventoryHidden = false;	// Not hidden
		g_InventoryMaximised = g_InvD[g_ino].bMax;
		if (invno != INV_CONF)	// Configuration window?
			ConstructInventory(FULL);	// Draw it up
		else {
			ConstructInventory(CONF);	// Draw it up
		}
	}
}

static void SetMenuGlobals(CONFINIT *ci) {
	g_InvD[INV_CONF].MinHicons = g_InvD[INV_CONF].MaxHicons = g_InvD[INV_CONF].NoofHicons = ci->h;
	g_InvD[INV_CONF].MaxVicons = g_InvD[INV_CONF].MinVicons = g_InvD[INV_CONF].NoofVicons = ci->v;
	g_InvD[INV_CONF].inventoryX = ci->x;
	g_InvD[INV_CONF].inventoryY = ci->y;
	cd.bExtraWin = ci->bExtraWin;
	cd.box = ci->Box;
	cd.NumBoxes = ci->NumBoxes;
	cd.ixHeading = ci->ixHeading;

	if (TinselV2) {
		if ((ci->ixHeading != NO_HEADING) && SysString(ci->ixHeading))
			g_InvD[INV_MENU].hInvTitle = SysString(ci->ixHeading);
		else
			g_InvD[INV_MENU].hInvTitle = NO_HEADING;
	}
}

/**
 * PopupConf
 */
extern void OpenMenu(CONFTYPE menuType) {
	int curX, curY;

	// In the DW 1 demo, don't allow any menu to be opened
	if (TinselV0)
		return;

	if (g_InventoryState != IDLE_INV)
		return;

	g_InvD[INV_CONF].resizable = false;
	g_InvD[INV_CONF].bMoveable = false;

	switch (menuType) {
	case MAIN_MENU:
		SetMenuGlobals(&ciOption);
		break;

	case SAVE_MENU:
		g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);	// Show VK when saving a game
		if (!TinselV2)
			SetCursorScreenXY(262, 91);
		SetMenuGlobals(&ciSave);
		cd.editableRgroup = true;
		FirstFile(0);
		break;

	case LOAD_MENU:
		SetMenuGlobals(&ciLoad);
		cd.editableRgroup = false;
		FirstFile(0);
		break;

	case RESTART_MENU:
		if (TinselV2)
			SetCursorScreenXY(360, 153);
		else if (_vm->getLanguage() == Common::JA_JPN)
			SetCursorScreenXY(180, 106);
		else
			SetCursorScreenXY(180, 90);

		SetMenuGlobals(&ciRestart);
		break;

	case SOUND_MENU:
		if (TinselV2)
			g_displayedLanguage = TextLanguage();
#if 1
		// FIXME: Hack to setup CONFBOX pointer to data in the global Config object
		if (TinselV2) {
			t2SoundBox[0].ival = &_vm->_config->_musicVolume;
			t2SoundBox[1].ival = &_vm->_config->_soundVolume;
			t2SoundBox[2].ival = &_vm->_config->_voiceVolume;
			t2SoundBox[3].ival = &_vm->_config->_textSpeed;
			t2SoundBox[4].ival = &_vm->_config->_useSubtitles;
		} else {
			t1SoundBox[0].ival = &_vm->_config->_musicVolume;
			t1SoundBox[1].ival = &_vm->_config->_soundVolume;
			t1SoundBox[2].ival = &_vm->_config->_voiceVolume;
		}
#endif
		SetMenuGlobals(&ciSound);
		break;

	case CONTROLS_MENU:
#if 1
		// FIXME: Hack to setup CONFBOX pointer to data in the global Config object
		controlBox[0].ival = &_vm->_config->_dclickSpeed;
		controlBox[2].ival = &_vm->_config->_swapButtons;
#endif
		SetMenuGlobals(&ciControl);
		break;

	case QUIT_MENU:
		if (TinselV2)
			SetCursorScreenXY(360, 153);
		else if (_vm->getLanguage() == Common::JA_JPN)
			SetCursorScreenXY(180, 106);
		else
			SetCursorScreenXY(180, 90);

		SetMenuGlobals(&ciQuit);
		break;

	case HOPPER_MENU1:
		PrimeSceneHopper();
		SetMenuGlobals(&ciHopper1);
		cd.editableRgroup = false;
		RememberChosenScene();
		FirstScene(0);
		break;

	case HOPPER_MENU2:
		SetMenuGlobals(&ciHopper2);
		cd.editableRgroup = false;
		SetChosenScene();
		FirstEntry(0);
		break;

	case SUBTITLES_MENU: {
		int hackOffset = 0;
		if (_vm->getFeatures() & GF_USE_3FLAGS) {
			hackOffset = 3;
			ciSubtitles.v = 6;
			ciSubtitles.Box = subtitlesBox3Flags;
			ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox3Flags);
		} else if (_vm->getFeatures() & GF_USE_4FLAGS) {
			hackOffset = 4;
			ciSubtitles.v = 6;
			ciSubtitles.Box = subtitlesBox4Flags;
			ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox4Flags);
		} else if (_vm->getFeatures() & GF_USE_5FLAGS) {
			hackOffset = 5;
			ciSubtitles.v = 6;
			ciSubtitles.Box = subtitlesBox5Flags;
			ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox5Flags);
		} else {
			hackOffset = 0;
			ciSubtitles.v = 3;
			ciSubtitles.Box = subtitlesBox;
			ciSubtitles.NumBoxes = ARRAYSIZE(subtitlesBox);
		}
#if 1
		// FIXME: Hack to setup CONFBOX pointer to data in the global Config object
		ciSubtitles.Box[hackOffset].ival = &_vm->_config->_textSpeed;
		ciSubtitles.Box[hackOffset+1].ival = &_vm->_config->_useSubtitles;
#endif

		SetMenuGlobals(&ciSubtitles);
		}
		break;

	case TOP_WINDOW:
		SetMenuGlobals(&ciTopWin);
		g_ino = INV_CONF;
		ConstructInventory(CONF);	// Draw it up
		g_InventoryState = BOGUS_INV;
		return;

	default:
		return;
	}

	if (g_heldItem != INV_NOICON)
		DelAuxCursor();			// no longer aux cursor

	PopUpInventory(INV_CONF);

	// Make initial box selections if appropriate
	if (menuType == SAVE_MENU || menuType == LOAD_MENU
			|| menuType == HOPPER_MENU1 || menuType == HOPPER_MENU2)
		Select(0, false);
	else if (menuType == SUBTITLES_MENU) {
		if (_vm->getFeatures() & GF_USE_3FLAGS) {
			// VERY quick dirty bodges
			if (_vm->_config->_language == TXT_FRENCH)
				Select(0, false);
			else if (_vm->_config->_language == TXT_GERMAN)
				Select(1, false);
			else
				Select(2, false);
		} else if (_vm->getFeatures() & GF_USE_4FLAGS) {
			Select(_vm->_config->_language-1, false);
		} else if (_vm->getFeatures() & GF_USE_5FLAGS) {
			Select(_vm->_config->_language, false);
		}
	}

	GetCursorXY(&curX, &curY, false);
	InvCursor(IC_AREA, curX, curY);
}

/**
 * Close down an inventory window.
 */
extern void KillInventory() {
	if (g_objArray[0] != NULL) {
		DumpObjArray();
		DumpDobjArray();
		DumpIconArray();
	}

	if (g_InventoryState == ACTIVE_INV) {
		EnableTags();
		if (TinselV2)
			EnablePointing();

		g_InvD[g_ino].bMax = g_InventoryMaximised;

		UnHideCursorTrails();
		_vm->divertKeyInput(NULL);
	}

	g_InventoryState = IDLE_INV;

	if (g_bReOpenMenu) {
		g_bReOpenMenu = false;
		OpenMenu(MAIN_MENU);

		// Write config changes
		_vm->_config->writeToDisk();

	} else if (g_ino == INV_CONF)
		InventoryIconCursor(false);

	if (TinselV2)
		// Pump up the volume
		if (g_ino == INV_CONV)
			_vm->_pcmMusic->unDim(false);

	g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);	// Hide VK after save dialog closes
}

extern void CloseInventory() {
	// If not active, ignore this
	if (g_InventoryState != ACTIVE_INV)
		return;

	// If hidden, a conversation action is still underway - ignore this
	if (g_InventoryHidden)
		return;

	// If conversation, this is a closeing event
	if (g_ino == INV_CONV)
		ConvAction(INV_CLOSEICON);

	KillInventory();

	RestoreMainCursor();
}



/**************************************************************************/
/************************ The inventory process ***************************/
/**************************************************************************/

/**
 * Redraws the icons if appropriate. Also handle button press/toggle effects
 */
extern void InventoryProcess(CORO_PARAM, const void *) {
	// COROUTINE
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	if (NumberOfLanguages() <= 1)
		g_bNoLanguage = true;

	while (1) {
		CORO_SLEEP(1);		// allow scheduling

		if (g_objArray[0] != NULL) {
			if (g_ItemsChanged && g_ino != INV_CONF && !g_InventoryHidden) {
				FillInInventory();

				// Needed when clicking on scroll bar.
				int	curX, curY;
				GetCursorXY(&curX, &curY, false);
				InvCursor(IC_AREA, curX, curY);

				g_ItemsChanged = false;
			}
			if (g_ino != INV_CONF) {
				for (int i = 0; i < MAX_ICONS; i++) {
					if (g_iconArray[i] != NULL)
						StepAnimScript(&g_iconAnims[i]);
				}
			}
			if (g_InvDragging == ID_MDCONT) {
				// Mixing desk control
				int sval, index, *pival;

				index = cd.selBox & ~IS_MASK;
				pival = cd.box[index].ival;
				sval = *pival;

				if (cd.selBox & IS_LEFT) {
					*pival -= cd.box[index].h;
					if (*pival < 0)
						*pival = 0;
				} else if (cd.selBox & IS_RIGHT) {
					*pival += cd.box[index].h;
					if (*pival > cd.box[index].w)
						*pival = cd.box[index].w;
				}

				if (sval != *pival) {
					SlideMSlider(0, (cd.selBox & IS_RIGHT) ? S_TIMEUP : S_TIMEDN);
				}
			}
		}

		if (g_buttonEffect.bButAnim) {
			assert(g_buttonEffect.box);
			if (g_buttonEffect.press) {
				if (g_buttonEffect.box->boxType == AAGBUT || g_buttonEffect.box->boxType == ARSGBUT)
					CORO_INVOKE_1(ButtonPress, g_buttonEffect.box);
				switch (g_buttonEffect.box->boxFunc) {
				case SAVEGAME:
					KillInventory();
					InvSaveGame();
					break;
				case LOADGAME:
					KillInventory();
					InvLoadGame();
					break;
				case IQUITGAME:
					_vm->quitGame();
					break;
				case CLOSEWIN:
					KillInventory();
					if ((cd.box == hopperBox1) || (cd.box == hopperBox2))
						FreeSceneHopper();
					break;
				case OPENLOAD:
					KillInventory();
					OpenMenu(LOAD_MENU);
					break;
				case OPENSAVE:
					KillInventory();
					OpenMenu(SAVE_MENU);
					break;
				case OPENREST:
					KillInventory();
					OpenMenu(RESTART_MENU);
					break;
				case OPENSOUND:
					KillInventory();
					OpenMenu(SOUND_MENU);
					break;
				case OPENCONT:
					KillInventory();
					OpenMenu(CONTROLS_MENU);
					break;
	#ifndef JAPAN
				case OPENSUBT:
					KillInventory();
					OpenMenu(SUBTITLES_MENU);
					break;
	#endif
				case OPENQUIT:
					KillInventory();
					OpenMenu(QUIT_MENU);
					break;
				case INITGAME:
					KillInventory();
					FnRestartGame();
					break;
				case CLANG:
					if (!LanguageChange())
						KillInventory();
					break;
				case RLANG:
					KillInventory();
					break;
				case HOPPER2:
					KillInventory();
					OpenMenu(HOPPER_MENU2);
					break;
				case BF_CHANGESCENE:
					KillInventory();
					HopAction();
					FreeSceneHopper();
					break;
				default:
					break;
				}
			} else
				CORO_INVOKE_1(ButtonToggle, g_buttonEffect.box);

			g_buttonEffect.bButAnim = false;
		}

	}
	CORO_END_CODE;
}

/**************************************************************************/
/*************** Drag stuff - Resizing and moving window ******************/
/**************************************************************************/

/**
 * Appears to find the nearest entry in slideStuff[] to the supplied
 * y-coordinate.
 */
static int NearestSlideY(int fity) {
	int nearDist = 1000;
	int thisDist;
	int nearI = 0;	// Index of nearest fit
	int i = 0;

	do {
		thisDist = ABS(g_slideStuff[i].y - fity);
		if (thisDist < nearDist) {
			nearDist = thisDist;
			nearI = i;
		}
	} while (g_slideStuff[++i].n != -1);
	return nearI;
}

/**
 * Gets called at the start and end of a drag on the slider, and upon
 * y-movement during such a drag.
 */
static void SlideSlider(int y, SSFN fn) {
	static int newY = 0, lasti = 0;	// FIXME: Avoid non-const global vars
	int gotoY, ati;

	// Only do this if there's a slider
	if (!g_SlideObject)
		return;

	switch (fn) {
	case S_START:			// Start of a drag on the slider
		newY = g_sliderYpos;
		lasti = NearestSlideY(g_sliderYpos);
		break;

	case S_SLIDE:			// Y-movement during drag
		newY = newY + y;		// New y-position

		if (newY < g_sliderYmin)
			gotoY = g_sliderYmin;	// Above top limit
		else if (newY > g_sliderYmax)
			gotoY = g_sliderYmax;	// Below bottom limit
		else
			gotoY = newY;		// Hunky-Dory

		// Move slider to new position
		MultiMoveRelXY(g_SlideObject, 0, gotoY - g_sliderYpos);
		g_sliderYpos = gotoY;

		// Re-draw icons if necessary
		ati = NearestSlideY(g_sliderYpos);
		if (ati != lasti) {
			g_InvD[g_ino].FirstDisp = g_slideStuff[ati].n;
			assert(g_InvD[g_ino].FirstDisp >= 0); // negative first displayed
			g_ItemsChanged = true;
			lasti = ati;
		}
		break;

	case S_END:			// End of a drag on the slider
		// Draw icons from new start icon
		ati = NearestSlideY(g_sliderYpos);
		g_InvD[g_ino].FirstDisp = g_slideStuff[ati].n;
		g_ItemsChanged = true;
		break;

	default:
		break;
	}
}

/**
 * Gets called at the start and end of a drag on the slider, and upon
 * y-movement during such a drag.
 */
static void SlideCSlider(int y, SSFN fn) {
	static int newY = 0;	// FIXME: Avoid non-const global vars
	int	gotoY;
	int	fc;

	// Only do this if there's a slider
	if (!g_SlideObject)
		return;

	switch (fn) {
	case S_START:			// Start of a drag on the slider
		newY = g_sliderYpos;
		break;

	case S_SLIDE:			// Y-movement during drag
		newY = newY + y;		// New y-position

		if (newY < g_sliderYmin)
			gotoY = g_sliderYmin;	// Above top limit
		else if (newY > g_sliderYmax)
			gotoY = g_sliderYmax;	// Below bottom limit
		else
			gotoY = newY;		// Hunky-Dory

		// Move slider to new position
		if (TinselV2)
			MultiMoveRelXY(g_SlideObject, 0, gotoY - g_sliderYpos);
		g_sliderYpos = gotoY;

		fc = cd.extraBase;

		if ((cd.box == saveBox || cd.box == loadBox))
			FirstFile((g_sliderYpos - g_sliderYmin) * (MAX_SAVED_FILES - NUM_RGROUP_BOXES) /
				(g_sliderYmax - g_sliderYmin));
		else if (cd.box == hopperBox1)
			FirstScene((g_sliderYpos - g_sliderYmin) * (g_numScenes - NUM_RGROUP_BOXES) / sliderRange);
		else if (cd.box == hopperBox2)
			FirstEntry((g_sliderYpos - g_sliderYmin) * (g_numEntries - NUM_RGROUP_BOXES) / sliderRange);

		// If extraBase has changed...
		if (fc != cd.extraBase) {
			AddBoxes(false);
			fc -= cd.extraBase;
			cd.selBox += fc;

			// Ensure within legal limits
			if (cd.selBox < 0)
				cd.selBox = 0;
			else if (cd.selBox >= NUM_RGROUP_BOXES)
				cd.selBox = NUM_RGROUP_BOXES-1;

			Select(cd.selBox, true);
		}
		break;

	case S_END:			// End of a drag on the slider
		break;

	default:
		break;
	}
}

/**
 * Gets called at the start and end of a drag on a mixing desk slider,
 * and upon x-movement during such a drag.
 */
static void SlideMSlider(int x, SSFN fn) {
	static int newX = 0;	// FIXME: Avoid non-const global vars
	int gotoX;
	int index, i;

	if (fn == S_END || fn == S_TIMEUP || fn == S_TIMEDN)
		;
	else if (!(cd.selBox & IS_SLIDER))
		return;

	// Work out the indices
	index = cd.selBox & ~IS_MASK;
	for (i = 0; i < g_numMdSlides; i++)
		if (g_mdSlides[i].num == index)
			break;
	assert(i < g_numMdSlides);

	switch (fn) {
	case S_START:			// Start of a drag on the slider
		// can use index as a throw-away value
		GetAniPosition(g_mdSlides[i].obj, &newX, &index);
		g_lX = g_sX = newX;
		break;

	case S_SLIDE:			// X-movement during drag
		if (x == 0)
			return;

		newX = newX + x;	// New x-position

		if (newX < g_mdSlides[i].min)
			gotoX = g_mdSlides[i].min;	// Below bottom limit
		else if (newX > g_mdSlides[i].max)
			gotoX = g_mdSlides[i].max;	// Above top limit
		else
			gotoX = newX;		// Hunky-Dory

		// Move slider to new position
		MultiMoveRelXY(g_mdSlides[i].obj, gotoX - g_sX, 0);
		g_sX = gotoX;

		if (g_lX != g_sX) {
			*cd.box[index].ival = (g_sX - g_mdSlides[i].min)*cd.box[index].w/SLIDE_RANGE;
			if (cd.box[index].boxFunc == MUSICVOL)
				SetMidiVolume(*cd.box[index].ival);
#ifdef MAC_OPTIONS
			if (cd.box[index].boxFunc == MASTERVOL)
				SetSystemVolume(*cd.box[index].ival);

			if (cd.box[index].boxFunc == SAMPVOL)
				SetSampleVolume(*cd.box[index].ival);
#endif
			g_lX = g_sX;
		}
		break;

	case S_TIMEUP:
	case S_TIMEDN:
		gotoX = SLIDE_RANGE*(*cd.box[index].ival)/cd.box[index].w;
		MultiSetAniX(g_mdSlides[i].obj, g_mdSlides[i].min+gotoX);

		if (cd.box[index].boxFunc == MUSICVOL)
			SetMidiVolume(*cd.box[index].ival);
#ifdef MAC_OPTIONS
		if (cd.box[index].boxFunc == MASTERVOL)
			SetSystemVolume(*cd.box[index].ival);

		if (cd.box[index].boxFunc == SAMPVOL)
			SetSampleVolume(*cd.box[index].ival);
#endif
		break;

	case S_END:			// End of a drag on the slider
		AddBoxes(false);	// Might change position slightly
		if (g_ino == INV_CONF && cd.box == subtitlesBox)
			Select(_vm->_config->_language, false);
		break;
	}
}

/**
 * Called from ChangeingSize() during re-sizing.
 */
static void GettingTaller() {
	if (g_SuppV) {
		g_Ychange += g_SuppV;
		if (g_Ycompensate == 'T')
			g_InvD[g_ino].inventoryY += g_SuppV;
		g_SuppV = 0;
	}
	while (g_Ychange > (ITEM_HEIGHT+1) && g_InvD[g_ino].NoofVicons < g_InvD[g_ino].MaxVicons) {
		g_Ychange -= (ITEM_HEIGHT+1);
		g_InvD[g_ino].NoofVicons++;
		if (g_Ycompensate == 'T')
			g_InvD[g_ino].inventoryY -= (ITEM_HEIGHT+1);
	}
	if (g_InvD[g_ino].NoofVicons < g_InvD[g_ino].MaxVicons) {
		g_SuppV = g_Ychange;
		g_Ychange = 0;
		if (g_Ycompensate == 'T')
			g_InvD[g_ino].inventoryY -= g_SuppV;
	}
}

/**
 * Called from ChangeingSize() during re-sizing.
 */
static void GettingShorter() {
	int StartNvi = g_InvD[g_ino].NoofVicons;
	int StartUv = g_SuppV;

	if (g_SuppV) {
		g_Ychange += (g_SuppV - (ITEM_HEIGHT+1));
		g_InvD[g_ino].NoofVicons++;
		g_SuppV = 0;
	}
	while (g_Ychange < -(ITEM_HEIGHT+1) && g_InvD[g_ino].NoofVicons > g_InvD[g_ino].MinVicons) {
		g_Ychange += (ITEM_HEIGHT+1);
		g_InvD[g_ino].NoofVicons--;
	}
	if (g_InvD[g_ino].NoofVicons > g_InvD[g_ino].MinVicons && g_Ychange) {
		g_SuppV = (ITEM_HEIGHT+1) + g_Ychange;
		g_InvD[g_ino].NoofVicons--;
		g_Ychange = 0;
	}
	if (g_Ycompensate == 'T')
		g_InvD[g_ino].inventoryY += (ITEM_HEIGHT+1)*(StartNvi - g_InvD[g_ino].NoofVicons) - (g_SuppV - StartUv);
}

/**
 * Called from ChangeingSize() during re-sizing.
 */
static void GettingWider() {
	int StartNhi = g_InvD[g_ino].NoofHicons;
	int StartUh = g_SuppH;

	if (g_SuppH) {
		g_Xchange += g_SuppH;
		g_SuppH = 0;
	}
	while (g_Xchange > (ITEM_WIDTH+1) && g_InvD[g_ino].NoofHicons < g_InvD[g_ino].MaxHicons) {
		g_Xchange -= (ITEM_WIDTH+1);
		g_InvD[g_ino].NoofHicons++;
	}
	if (g_InvD[g_ino].NoofHicons < g_InvD[g_ino].MaxHicons) {
		g_SuppH = g_Xchange;
		g_Xchange = 0;
	}
	if (g_Xcompensate == 'L')
		g_InvD[g_ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - g_InvD[g_ino].NoofHicons) - (g_SuppH - StartUh);
}

/**
 * Called from ChangeingSize() during re-sizing.
 */
static void GettingNarrower() {
	int StartNhi = g_InvD[g_ino].NoofHicons;
	int StartUh = g_SuppH;

	if (g_SuppH) {
		g_Xchange += (g_SuppH - (ITEM_WIDTH+1));
		g_InvD[g_ino].NoofHicons++;
		g_SuppH = 0;
	}
	while (g_Xchange < -(ITEM_WIDTH+1) && g_InvD[g_ino].NoofHicons > g_InvD[g_ino].MinHicons) {
		g_Xchange += (ITEM_WIDTH+1);
		g_InvD[g_ino].NoofHicons--;
	}
	if (g_InvD[g_ino].NoofHicons > g_InvD[g_ino].MinHicons && g_Xchange) {
		g_SuppH = (ITEM_WIDTH+1) + g_Xchange;
		g_InvD[g_ino].NoofHicons--;
		g_Xchange = 0;
	}
	if (g_Xcompensate == 'L')
		g_InvD[g_ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - g_InvD[g_ino].NoofHicons) - (g_SuppH - StartUh);
}


/**
 * Called from Xmovement()/Ymovement() during re-sizing.
 */
static void ChangeingSize() {
	/* Make it taller or shorter if necessary. */
	if (g_Ychange > 0)
		GettingTaller();
	else if (g_Ychange < 0)
		GettingShorter();

	/* Make it wider or narrower if necessary. */
	if (g_Xchange > 0)
		GettingWider();
	else if (g_Xchange < 0)
		GettingNarrower();

	ConstructInventory(EMPTY);
}

/**
 * Called from cursor module when cursor moves while inventory is up.
 */
extern void Xmovement(int x) {
	int aniX, aniY;
	int i;

	if (x && g_objArray[0] != NULL) {
		switch (g_InvDragging) {
		case ID_MOVE:
			GetAniPosition(g_objArray[0], &g_InvD[g_ino].inventoryX, &aniY);
			g_InvD[g_ino].inventoryX +=x;
			MultiSetAniX(g_objArray[0], g_InvD[g_ino].inventoryX);
			for (i = 1; i < MAX_WCOMP && g_objArray[i]; i++)
				MultiMoveRelXY(g_objArray[i], x, 0);
			for (i = 0; i < MAX_ICONS && g_iconArray[i]; i++)
				MultiMoveRelXY(g_iconArray[i], x, 0);
			break;

		case ID_LEFT:
		case ID_TLEFT:
		case ID_BLEFT:
			g_Xchange -= x;
			ChangeingSize();
			break;

		case ID_RIGHT:
		case ID_TRIGHT:
		case ID_BRIGHT:
			g_Xchange += x;
			ChangeingSize();
			break;

		case ID_NONE:
			GetCursorXY(&aniX, &aniY, false);
			InvCursor(IC_AREA, aniX, aniY);
			break;

		case ID_MDCONT:
			SlideMSlider(x, S_SLIDE);
			break;

		default:
			break;
		}
	}
}

/**
 * Called from cursor module when cursor moves while inventory is up.
 */
extern void Ymovement(int y) {
	int aniX, aniY;
	int i;

	if (y && g_objArray[0] != NULL) {
		switch (g_InvDragging) {
		case ID_MOVE:
			GetAniPosition(g_objArray[0], &aniX, &g_InvD[g_ino].inventoryY);
			g_InvD[g_ino].inventoryY +=y;
			MultiSetAniY(g_objArray[0], g_InvD[g_ino].inventoryY);
			for (i = 1; i < MAX_WCOMP && g_objArray[i]; i++)
				MultiMoveRelXY(g_objArray[i], 0, y);
			for (i = 0; i < MAX_ICONS && g_iconArray[i]; i++)
				MultiMoveRelXY(g_iconArray[i], 0, y);
			break;

		case ID_SLIDE:
			SlideSlider(y, S_SLIDE);
			break;

		case ID_CSLIDE:
			SlideCSlider(y, S_SLIDE);
			break;

		case ID_BOTTOM:
		case ID_BLEFT:
		case ID_BRIGHT:
			g_Ychange += y;
			ChangeingSize();
			break;

		case ID_TOP:
		case ID_TLEFT:
		case ID_TRIGHT:
			g_Ychange -= y;
			ChangeingSize();
			break;

		case ID_NONE:
			GetCursorXY(&aniX, &aniY, false);
			InvCursor(IC_AREA, aniX, aniY);
			break;

		default:
			break;
		}
	}
}

/**
 * Called when a drag is commencing.
 */
static void InvDragStart() {
	int curX, curY;		// cursor's animation position

	GetCursorXY(&curX, &curY, false);

	/*
	 * Do something different for Save/Restore screens
	 */
	if (g_ino == INV_CONF) {
		int	whichbox;

		whichbox = WhichMenuBox(curX, curY, true);

		if (whichbox == IB_SLIDE) {
			g_InvDragging = ID_CSLIDE;
			SlideCSlider(0, S_START);
		} else if (whichbox > 0 && (whichbox & IS_MASK)) {
			g_InvDragging = ID_MDCONT;	// Mixing desk control
			cd.selBox = whichbox;
			SlideMSlider(0, S_START);
		}
		return;
	}

	/*
	 * Normal operation
	 */
	switch (InvArea(curX, curY)) {
	case I_HEADER:
		if (g_InvD[g_ino].bMoveable) {
			g_InvDragging = ID_MOVE;
		}
		break;

	case I_SLIDE:
		g_InvDragging = ID_SLIDE;
		SlideSlider(0, S_START);
		break;

	case I_BOTTOM:
		if (g_InvD[g_ino].resizable) {
			g_Ychange = 0;
			g_InvDragging = ID_BOTTOM;
			g_Ycompensate = 'B';
		}
		break;

	case I_TOP:
		if (g_InvD[g_ino].resizable) {
			g_Ychange = 0;
			g_InvDragging = ID_TOP;
			g_Ycompensate = 'T';
		}
		break;

	case I_LEFT:
		if (g_InvD[g_ino].resizable) {
			g_Xchange = 0;
			g_InvDragging = ID_LEFT;
			g_Xcompensate = 'L';
		}
		break;

	case I_RIGHT:
		if (g_InvD[g_ino].resizable) {
			g_Xchange = 0;
			g_InvDragging = ID_RIGHT;
			g_Xcompensate = 'R';
		}
		break;

	case I_TLEFT:
		if (g_InvD[g_ino].resizable) {
			g_Ychange = 0;
			g_Ycompensate = 'T';
			g_Xchange = 0;
			g_Xcompensate = 'L';
			g_InvDragging = ID_TLEFT;
		}
		break;

	case I_TRIGHT:
		if (g_InvD[g_ino].resizable) {
			g_Ychange = 0;
			g_Ycompensate = 'T';
			g_Xchange = 0;
			g_Xcompensate = 'R';
			g_InvDragging = ID_TRIGHT;
		}
		break;

	case I_BLEFT:
		if (g_InvD[g_ino].resizable) {
			g_Ychange = 0;
			g_Ycompensate = 'B';
			g_Xchange = 0;
			g_Xcompensate = 'L';
			g_InvDragging = ID_BLEFT;
		}
		break;

	case I_BRIGHT:
		if (g_InvD[g_ino].resizable) {
			g_Ychange = 0;
			g_Ycompensate = 'B';
			g_Xchange = 0;
			g_Xcompensate = 'R';
			g_InvDragging = ID_BRIGHT;
		}
		break;
	}
}

/**
 * Called when a drag is over.
 */
static void InvDragEnd() {
	int curX, curY;		// cursor's animation position

	GetCursorXY(&curX, &curY, false);

	if (g_InvDragging != ID_NONE) {
		if (g_InvDragging == ID_SLIDE) {
			SlideSlider(0, S_END);
		} else if (g_InvDragging == ID_CSLIDE) {
			;	// No action
		} else if (g_InvDragging == ID_MDCONT) {
			SlideMSlider(0, S_END);
		} else if (g_InvDragging == ID_MOVE) {
			;	// No action
		} else {
			// Were re-sizing. Redraw the whole thing.
			DumpDobjArray();
			DumpObjArray();
			ConstructInventory(FULL);

			// If this was the maximised, it no longer is!
			if (g_InventoryMaximised) {
				g_InventoryMaximised = false;
				g_InvD[g_ino].otherX = g_InvD[g_ino].inventoryX;
				g_InvD[g_ino].otherY = g_InvD[g_ino].inventoryY;
			}
		}

		g_InvDragging = ID_NONE;
		ProcessedProvisional();
	}

	// Cursor could well now be inappropriate
	InvCursor(IC_AREA, curX, curY);

	g_Xchange = g_Ychange = 0;		// Probably no need, but does no harm!
}

static bool MenuDown(int lines) {
	if (cd.box == loadBox || cd.box == saveBox) {
		if (cd.extraBase < MAX_SAVED_FILES - NUM_RGROUP_BOXES) {
			FirstFile(cd.extraBase + lines);
			AddBoxes(true);
			return true;
		}
	} else if (cd.box == hopperBox1) {
		if (cd.extraBase < g_numScenes - NUM_RGROUP_BOXES) {
			FirstScene(cd.extraBase + lines);
			AddBoxes(true);
			return true;
		}
	} else if (cd.box == hopperBox2) {
		if (cd.extraBase < g_numEntries - NUM_RGROUP_BOXES) {
			FirstEntry(cd.extraBase + lines);
			AddBoxes(true);
			return true;
		}
	}
	return false;
}

static bool MenuUp(int lines) {
	if (cd.extraBase > 0) {
		if (cd.box == loadBox || cd.box == saveBox)
			FirstFile(cd.extraBase - lines);
		else if (cd.box == hopperBox1)
			FirstScene(cd.extraBase - lines);
		else if (cd.box == hopperBox2)
			FirstEntry(cd.extraBase - lines);
		else
			return false;

		AddBoxes(true);
		return true;
	}
	return false;
}

static void MenuRollDown() {
	if (MenuDown(1)) {
		if (cd.selBox > 0)
			cd.selBox--;
		Select(cd.selBox, true);
	}
}

static void MenuRollUp() {
	if (MenuUp(1)) {
		if (cd.selBox < NUM_RGROUP_BOXES - 1)
			cd.selBox++;
		Select(cd.selBox, true);
	}
}

static void MenuPageDown() {
	if (MenuDown(NUM_RGROUP_BOXES - 1)) {
		cd.selBox = NUM_RGROUP_BOXES - 1;
		Select(cd.selBox, true);
	}
}

static void MenuPageUp() {
	if (MenuUp(NUM_RGROUP_BOXES - 1)) {
		cd.selBox = 0;
		Select(cd.selBox, true);
	}
}

static void InventoryDown() {
	// This code is a copy of the IB_SLIDE_DOWN case in InvWalkTo
	// TODO: So share this duplicate code
	if (g_InvD[g_ino].NoofVicons == 1)
		if (g_InvD[g_ino].FirstDisp + g_InvD[g_ino].NoofHicons*g_InvD[g_ino].NoofVicons < g_InvD[g_ino].NoofItems)
			g_InvD[g_ino].FirstDisp += g_InvD[g_ino].NoofHicons;
	for (int i = 1; i < g_InvD[g_ino].NoofVicons; i++) {
		if (g_InvD[g_ino].FirstDisp + g_InvD[g_ino].NoofHicons*g_InvD[g_ino].NoofVicons < g_InvD[g_ino].NoofItems)
			g_InvD[g_ino].FirstDisp += g_InvD[g_ino].NoofHicons;
	}
	g_ItemsChanged = true;
}

static void InventoryUp() {
	// This code is a copy of the I_SLIDE_UP case in InvWalkTo
	// TODO: So share this duplicate code
	if (g_InvD[g_ino].NoofVicons == 1)
		g_InvD[g_ino].FirstDisp -= g_InvD[g_ino].NoofHicons;
	for (int i = 1; i < g_InvD[g_ino].NoofVicons; i++)
		g_InvD[g_ino].FirstDisp -= g_InvD[g_ino].NoofHicons;
	if (g_InvD[g_ino].FirstDisp < 0)
		g_InvD[g_ino].FirstDisp = 0;
	g_ItemsChanged = true;
}

/**************************************************************************/
/************** Incoming events - further processing **********************/
/**************************************************************************/

/**
 * MenuAction
 */
static void MenuAction(int i, bool dbl) {

	if (i >= 0) {
		switch (cd.box[i].boxType) {
		case FLIP:
			if (dbl) {
				*(cd.box[i].ival) ^= 1;	// XOR with true
				AddBoxes(false);
			}
			break;

		case TOGGLE:
		case TOGGLE1:
		case TOGGLE2:
			if (!g_buttonEffect.bButAnim) {
				g_buttonEffect.bButAnim = true;
				g_buttonEffect.box = &cd.box[i];
				g_buttonEffect.press = false;
			}
			break;

		case RGROUP:
			if (dbl) {
				// Already highlighted
				switch (cd.box[i].boxFunc) {
				case SAVEGAME:
					KillInventory();
					InvSaveGame();
					break;
				case LOADGAME:
					KillInventory();
					InvLoadGame();
					break;
				case HOPPER2:
					KillInventory();
					OpenMenu(HOPPER_MENU2);
					break;
				case BF_CHANGESCENE:
					KillInventory();
					HopAction();
					FreeSceneHopper();
					break;
				default:
					break;
				}
			} else {
				Select(i, false);
			}
			break;

		case FRGROUP:
			if (dbl) {
				Select(i, false);
				LanguageChange();
			} else {
				Select(i, false);
			}
			break;

		case AAGBUT:
		case ARSGBUT:
		case ARSBUT:
		case AABUT:
		case AATBUT:
			if (g_buttonEffect.bButAnim)
				break;

			g_buttonEffect.bButAnim = true;
			g_buttonEffect.box = &cd.box[i];
			g_buttonEffect.press = true;
			break;
		default:
			break;
		}
	} else {
		ConfActionSpecial(i);
	}
}

static void ConfActionSpecial(int i) {
	switch (i) {
	case IB_NONE:
		break;
	case IB_UP:	// Scroll up
		if (cd.extraBase > 0) {
			if ((cd.box == loadBox) || (cd.box == saveBox))
				FirstFile(cd.extraBase - 1);
			else if (cd.box == hopperBox1)
				FirstScene(cd.extraBase - 1);
			else if (cd.box == hopperBox2)
				FirstEntry(cd.extraBase - 1);

			AddBoxes(true);
			if (cd.selBox < NUM_RGROUP_BOXES - 1)
				cd.selBox += 1;
			Select(cd.selBox, true);
		}
		break;
	case IB_DOWN:	// Scroll down
		if ((cd.box == loadBox) || (cd.box == saveBox)) {
			if (cd.extraBase < MAX_SAVED_FILES - NUM_RGROUP_BOXES) {
				FirstFile(cd.extraBase + 1);
				AddBoxes(true);
				if (cd.selBox)
					cd.selBox -= 1;
				Select(cd.selBox, true);
			}
		} else if (cd.box == hopperBox1) {
			if (cd.extraBase < g_numScenes - NUM_RGROUP_BOXES) {
				FirstScene(cd.extraBase + 1);
				AddBoxes(true);
				if (cd.selBox)
					cd.selBox -= 1;
				Select(cd.selBox, true);
			}
		} else if (cd.box == hopperBox2) {
			if (cd.extraBase < g_numEntries - NUM_RGROUP_BOXES) {
				FirstEntry(cd.extraBase + 1);
				AddBoxes(true);
				if (cd.selBox)
					cd.selBox -= 1;
				Select(cd.selBox, true);
			}
		}
		break;

	case IB_SLIDE_UP:
		MenuPageUp();
		break;

	case IB_SLIDE_DOWN:
		MenuPageDown();
		break;
	}
}
// SLIDE_UP and SLIDE_DOWN on d click??????

static void InvPutDown(int index) {
	int aniX, aniY;
			// index is the drop position
	int hiIndex;	// Current position of held item (if in)

	// Find where the held item is positioned in this inventory (if it is)
	for (hiIndex = 0; hiIndex < g_InvD[g_ino].NoofItems; hiIndex++)
		if (g_InvD[g_ino].contents[hiIndex] == g_heldItem)
			break;

	// If drop position would leave a gap, move it up
	if (index >= g_InvD[g_ino].NoofItems) {
		if (hiIndex == g_InvD[g_ino].NoofItems)	// Not in, add it
			index = g_InvD[g_ino].NoofItems;
		else
			index = g_InvD[g_ino].NoofItems - 1;
	}

	if (hiIndex == g_InvD[g_ino].NoofItems) {	// Not in, add it
		if (g_InvD[g_ino].NoofItems < g_InvD[g_ino].MaxInvObj) {
			g_InvD[g_ino].NoofItems++;

			// Don't leave it in the other inventory!
			if (InventoryPos(g_heldItem) != INV_HELDNOTIN)
				RemFromInventory(g_ino == INV_1 ? INV_2 : INV_1, g_heldItem);
		} else {
			// No room at the inn!
			return;
		}
	}

	// Position it in the inventory
	if (index < hiIndex) {
		memmove(&g_InvD[g_ino].contents[index + 1], &g_InvD[g_ino].contents[index], (hiIndex-index)*sizeof(int));
		g_InvD[g_ino].contents[index] = g_heldItem;
	} else if (index > hiIndex) {
		memmove(&g_InvD[g_ino].contents[hiIndex], &g_InvD[g_ino].contents[hiIndex+1], (index-hiIndex)*sizeof(int));
		g_InvD[g_ino].contents[index] = g_heldItem;
	} else {
		g_InvD[g_ino].contents[index] = g_heldItem;
	}

	g_heldItem = INV_NOICON;
	g_ItemsChanged = true;
	DelAuxCursor();
	RestoreMainCursor();
	GetCursorXY(&aniX, &aniY, false);
	InvCursor(IC_DROP, aniX, aniY);
}

static void InvPdProcess(CORO_PARAM, const void *param) {
	// COROUTINE
	CORO_BEGIN_CONTEXT;
	CORO_END_CONTEXT(_ctx);

	CORO_BEGIN_CODE(_ctx);

	GetToken(TOKEN_LEFT_BUT);
	CORO_SLEEP(_vm->_config->_dclickSpeed+1);
	FreeToken(TOKEN_LEFT_BUT);

	// get the stuff copied to process when it was created
	const int *pindex = (const int *)param;

	InvPutDown(*pindex);

	CORO_END_CODE;
}

static void InvPickup(int index) {
	INV_OBJECT *invObj;

	// Do nothing if not clicked on anything
	if (index == NOOBJECT)
		return;

	// If not holding anything
	if (g_heldItem == INV_NOICON && g_InvD[g_ino].contents[index] &&
			(!TinselV2 || g_InvD[g_ino].contents[index] != g_heldItem)) {
		// Pick-up
		invObj = GetInvObject(g_InvD[g_ino].contents[index]);
		g_thisIcon = g_InvD[g_ino].contents[index];
		if (TinselV2)
			InvTinselEvent(invObj, PICKUP, INV_PICKUP, index);
		else if (invObj->hScript)
			InvTinselEvent(invObj, WALKTO, INV_PICKUP, index);

	} else if (g_heldItem != INV_NOICON) {
		// Put-down
		invObj = GetInvObject(g_heldItem);

		// If DROPCODE set, send event, otherwise it's a putdown
		if (invObj->attribute & IO_DROPCODE && invObj->hScript)
			InvTinselEvent(invObj, PUTDOWN, INV_PICKUP, index);

		else if (!(invObj->attribute & IO_ONLYINV1 && g_ino != INV_1)
				&& !(invObj->attribute & IO_ONLYINV2 && g_ino != INV_2)) {
			if (TinselV2)
				InvPutDown(index);
			else
				CoroScheduler.createProcess(PID_TCODE, InvPdProcess, &index, sizeof(index));
		}
	}
}

/**
 * Handle WALKTO event (Pick up/put down event)
 */
static void InvWalkTo(const Common::Point &coOrds) {
	int i;

	switch (InvArea(coOrds.x, coOrds.y)) {
	case I_NOTIN:
		if (g_ino == INV_CONV)
			ConvAction(INV_CLOSEICON);
		if ((cd.box == hopperBox1) || (cd.box == hopperBox2))
			FreeSceneHopper();
		KillInventory();
		break;

	case I_SLIDE_UP:
		if (g_InvD[g_ino].NoofVicons == 1)
			g_InvD[g_ino].FirstDisp -= g_InvD[g_ino].NoofHicons;
		for (i = 1; i < g_InvD[g_ino].NoofVicons; i++)
			g_InvD[g_ino].FirstDisp -= g_InvD[g_ino].NoofHicons;
		if (g_InvD[g_ino].FirstDisp < 0)
			g_InvD[g_ino].FirstDisp = 0;
		g_ItemsChanged = true;
		break;

	case I_UP:
		g_InvD[g_ino].FirstDisp -= g_InvD[g_ino].NoofHicons;
		if (g_InvD[g_ino].FirstDisp < 0)
			g_InvD[g_ino].FirstDisp = 0;
		g_ItemsChanged = true;
		break;

	case I_SLIDE_DOWN:
		if (g_InvD[g_ino].NoofVicons == 1)
			if (g_InvD[g_ino].FirstDisp + g_InvD[g_ino].NoofHicons*g_InvD[g_ino].NoofVicons < g_InvD[g_ino].NoofItems)
				g_InvD[g_ino].FirstDisp += g_InvD[g_ino].NoofHicons;
		for (i = 1; i < g_InvD[g_ino].NoofVicons; i++) {
			if (g_InvD[g_ino].FirstDisp + g_InvD[g_ino].NoofHicons*g_InvD[g_ino].NoofVicons < g_InvD[g_ino].NoofItems)
				g_InvD[g_ino].FirstDisp += g_InvD[g_ino].NoofHicons;
		}
		g_ItemsChanged = true;
		break;

	case I_DOWN:
		if (g_InvD[g_ino].FirstDisp + g_InvD[g_ino].NoofHicons*g_InvD[g_ino].NoofVicons < g_InvD[g_ino].NoofItems) {
			g_InvD[g_ino].FirstDisp += g_InvD[g_ino].NoofHicons;
			g_ItemsChanged = true;
		}
		break;

	case I_BODY:
		if (g_ino == INV_CONF) {
			if (!g_InventoryHidden)
				MenuAction(WhichMenuBox(coOrds.x, coOrds.y, false), false);
		} else {
			Common::Point pt = coOrds;
			i = InvItem(pt, false);

			// To cater for drop in dead space between icons,
			// look 1 pixel right, then 1 down, then 1 right and down.
			if (i == INV_NOICON && g_heldItem != INV_NOICON &&
					(g_ino == INV_1 || g_ino == INV_2)) {
				pt.x += 1;				// 1 to the right
				i = InvItem(pt, false);
				if (i == INV_NOICON) {
					pt.x -= 1;			// 1 down
					pt.y += 1;
					i = InvItem(pt, false);
					if (i == INV_NOICON) {
						pt.x += 1;		// 1 down-right
						i = InvItem(pt, false);
					}
				}
			}

			if (g_ino == INV_CONV) {
				ConvAction(i);
			} else
				InvPickup(i);
		}
		break;
	}
}

static void InvAction() {
	int index;
	INV_OBJECT *invObj;
	int aniX, aniY;
	int i;

	GetCursorXY(&aniX, &aniY, false);

	switch (InvArea(aniX, aniY)) {
	case I_BODY:
		if (g_ino == INV_CONF) {
			if (!g_InventoryHidden)
				MenuAction(WhichMenuBox(aniX, aniY, false), true);
		} else if (g_ino == INV_CONV) {
			index = InvItem(&aniX, &aniY, false);
			ConvAction(index);
		} else {
			index = InvItem(&aniX, &aniY, false);
			if (index != INV_NOICON) {
				if (g_InvD[g_ino].contents[index] && g_InvD[g_ino].contents[index] != g_heldItem) {
					invObj = GetInvObject(g_InvD[g_ino].contents[index]);
					if (TinselV2)
						g_thisIcon = g_InvD[g_ino].contents[index];
					if (TinselV2 || (invObj->hScript))
						InvTinselEvent(invObj, ACTION, INV_ACTION, index);
				}
			}
		}
		break;

	case I_HEADER:	// Maximise/unmaximise inventory
		if (!g_InvD[g_ino].resizable)
			break;

		if (!g_InventoryMaximised) {
			g_InvD[g_ino].sNoofHicons = g_InvD[g_ino].NoofHicons;
			g_InvD[g_ino].sNoofVicons = g_InvD[g_ino].NoofVicons;
			g_InvD[g_ino].NoofHicons = g_InvD[g_ino].MaxHicons;
			g_InvD[g_ino].NoofVicons = g_InvD[g_ino].MaxVicons;
			g_InventoryMaximised = true;

			i = g_InvD[g_ino].inventoryX;
			g_InvD[g_ino].inventoryX = g_InvD[g_ino].otherX;
			g_InvD[g_ino].otherX = i;
			i = g_InvD[g_ino].inventoryY;
			g_InvD[g_ino].inventoryY = g_InvD[g_ino].otherY;
			g_InvD[g_ino].otherY = i;
		} else {
			g_InvD[g_ino].NoofHicons = g_InvD[g_ino].sNoofHicons;
			g_InvD[g_ino].NoofVicons = g_InvD[g_ino].sNoofVicons;
			g_InventoryMaximised = false;

			i = g_InvD[g_ino].inventoryX;
			g_InvD[g_ino].inventoryX = g_InvD[g_ino].otherX;
			g_InvD[g_ino].otherX = i;
			i = g_InvD[g_ino].inventoryY;
			g_InvD[g_ino].inventoryY = g_InvD[g_ino].otherY;
			g_InvD[g_ino].otherY = i;
		}

		// Delete current, and re-draw
		DumpDobjArray();
		DumpObjArray();
		ConstructInventory(FULL);
		break;

	case I_UP:
		g_InvD[g_ino].FirstDisp -= g_InvD[g_ino].NoofHicons;
		if (g_InvD[g_ino].FirstDisp < 0)
			g_InvD[g_ino].FirstDisp = 0;
		g_ItemsChanged = true;
		break;
	case I_DOWN:
		if (g_InvD[g_ino].FirstDisp + g_InvD[g_ino].NoofHicons*g_InvD[g_ino].NoofVicons < g_InvD[g_ino].NoofItems) {
			g_InvD[g_ino].FirstDisp += g_InvD[g_ino].NoofHicons;
			g_ItemsChanged = true;
		}
		break;
	}

}

static void InvLook(const Common::Point &coOrds) {
	int index;
	INV_OBJECT *invObj;
	Common::Point pt = coOrds;

	switch (InvArea(pt.x, pt.y)) {
	case I_BODY:
		index = InvItem(pt, false);
		if (index != INV_NOICON) {
			if (g_InvD[g_ino].contents[index] && g_InvD[g_ino].contents[index] != g_heldItem) {
				invObj = GetInvObject(g_InvD[g_ino].contents[index]);
				if (invObj->hScript)
					InvTinselEvent(invObj, LOOK, INV_LOOK, index);
			}
		}
		break;

	case I_NOTIN:
		if (g_ino == INV_CONV)
			ConvAction(INV_CLOSEICON);
		KillInventory();
		break;
	}
}


/**************************************************************************/
/********************* Incoming events ************************************/
/**************************************************************************/

extern void EventToInventory(PLR_EVENT pEvent, const Common::Point &coOrds) {
	if (g_InventoryHidden)
		return;

	switch (pEvent) {
	case PLR_PROV_WALKTO:
		if (MenuActive()) {
			ProcessedProvisional();
			InvWalkTo(coOrds);
		}
		break;

	case PLR_WALKTO:		// PLR_SLEFT
		InvWalkTo(coOrds);
		break;

	case INV_LOOK:			// PLR_SRIGHT
		if (MenuActive())
			InvWalkTo(coOrds);
		else
			InvLook(coOrds);
		break;

	case PLR_ACTION:		// PLR_DLEFT
		if (g_InvDragging != ID_MDCONT)
			InvDragEnd();
		InvAction();
		break;

	case PLR_DRAG1_START:		// Left drag start
		InvDragStart();
		break;

	case PLR_DRAG1_END:		// Left drag end
		InvDragEnd();
		break;

	case PLR_ESCAPE:
		if (MenuActive()) {
			if (cd.box != optionBox && cd.box != hopperBox1 && cd.box != hopperBox2)
				g_bReOpenMenu = true;
			if ((cd.box == hopperBox1) || (cd.box == hopperBox2))
				FreeSceneHopper();
		}
		CloseInventory();
		break;

	case PLR_PGDN:
		if (g_ino == INV_MENU) {
			// Load or Save screen
			MenuPageDown();
		} else {
			// Inventory window
			InventoryDown();
		}
		break;

	case PLR_PGUP:
		if (g_ino == INV_MENU) {
			// Load or Save screen
			MenuPageUp();
		} else {
			// Inventory window
			InventoryUp();
		}
		break;

	case PLR_WHEEL_DOWN:
		if (g_ino == INV_MENU) {
			// Load or Save screen
			MenuRollDown();
		} else {
			// Inventory window
			InventoryDown();
		}
		break;

	case PLR_WHEEL_UP:
		if (g_ino == INV_MENU) {
			// Load or Save screen
			MenuRollUp();
		} else {
			// Inventory window
			InventoryUp();
		}
		break;

	case PLR_HOME:
		if (g_ino == INV_MENU) {
			// Load or Save screen
			if (cd.box == loadBox || cd.box == saveBox)
				FirstFile(0);
			else if (cd.box == hopperBox1)
				FirstScene(0);
			else if (cd.box == hopperBox2)
				FirstEntry(0);
			else
				break;

			AddBoxes(true);
			cd.selBox = 0;
			Select(cd.selBox, true);
		} else {
			// Inventory window
			g_InvD[g_ino].FirstDisp = 0;
			g_ItemsChanged = true;
		}
		break;

	case PLR_END:
		if (g_ino == INV_MENU) {
			// Load or Save screen
			if (cd.box == loadBox || cd.box == saveBox)
				FirstFile(MAX_SAVED_FILES);	// Will get reduced to appropriate value
			else if (cd.box == hopperBox1)
				FirstScene(g_numScenes);		// Will get reduced to appropriate value
			else if (cd.box == hopperBox2)
				FirstEntry(g_numEntries);		// Will get reduced to appropriate value
			else
				break;

			AddBoxes(true);
			cd.selBox = 0;
			Select(cd.selBox, true);
		} else {
			// Inventory window
			g_InvD[g_ino].FirstDisp = g_InvD[g_ino].NoofItems - g_InvD[g_ino].NoofHicons*g_InvD[g_ino].NoofVicons;
			if (g_InvD[g_ino].FirstDisp < 0)
				g_InvD[g_ino].FirstDisp = 0;
			g_ItemsChanged = true;
		}
		break;
	default:
		break;
	}
}

/**************************************************************************/
/************************* Odds and Ends **********************************/
/**************************************************************************/

/**
 * Called from Glitter function invdepict()
 * Changes (permanently) the animation film for that object.
 */
extern void SetObjectFilm(int object, SCNHANDLE hFilm) {
	INV_OBJECT *invObj;

	invObj = GetInvObject(object);
	invObj->hIconFilm = hFilm;

	if (g_heldItem != object)
		g_ItemsChanged = true;
}

/**
 * (Un)serialize the inventory data for save/restore game.
 */
extern void syncInvInfo(Common::Serializer &s) {
	for (int i = 0; i < NUM_INV; i++) {
		s.syncAsSint32LE(g_InvD[i].MinHicons);
		s.syncAsSint32LE(g_InvD[i].MinVicons);
		s.syncAsSint32LE(g_InvD[i].MaxHicons);
		s.syncAsSint32LE(g_InvD[i].MaxVicons);
		s.syncAsSint32LE(g_InvD[i].NoofHicons);
		s.syncAsSint32LE(g_InvD[i].NoofVicons);
		for (int j = 0; j < MAX_ININV; j++) {
			s.syncAsSint32LE(g_InvD[i].contents[j]);
		}
		s.syncAsSint32LE(g_InvD[i].NoofItems);
		s.syncAsSint32LE(g_InvD[i].FirstDisp);
		s.syncAsSint32LE(g_InvD[i].inventoryX);
		s.syncAsSint32LE(g_InvD[i].inventoryY);
		s.syncAsSint32LE(g_InvD[i].otherX);
		s.syncAsSint32LE(g_InvD[i].otherY);
		s.syncAsSint32LE(g_InvD[i].MaxInvObj);
		s.syncAsSint32LE(g_InvD[i].hInvTitle);
		s.syncAsSint32LE(g_InvD[i].resizable);
		s.syncAsSint32LE(g_InvD[i].bMoveable);
		s.syncAsSint32LE(g_InvD[i].sNoofHicons);
		s.syncAsSint32LE(g_InvD[i].sNoofVicons);
		s.syncAsSint32LE(g_InvD[i].bMax);
	}

	if (TinselV2) {
		for (int i = 0; i < g_numObjects; ++i)
			s.syncAsUint32LE(g_invFilms[i]);
		s.syncAsUint32LE(g_heldFilm);
	}
}

/**************************************************************************/
/************************ Initialisation stuff ****************************/
/**************************************************************************/

/**
 * Called from PlayGame(), stores handle to inventory objects' data -
 * its id, animation film and Glitter script.
 */
// Note: the SCHANDLE type here has been changed to a void*
extern void RegisterIcons(void *cptr, int num) {
	g_numObjects = num;
	g_invObjects = (INV_OBJECT *) cptr;

	if (TinselV0) {
		// In Tinsel 0, the INV_OBJECT structure doesn't have an attributes field, so we
		// need to 'unpack' the source structures into the standard Tinsel v1/v2 format
		MEM_NODE *node = MemoryAllocFixed(g_numObjects * sizeof(INV_OBJECT));
		assert(node);
		g_invObjects = (INV_OBJECT *)MemoryDeref(node);
		assert(g_invObjects);
		byte *srcP = (byte *)cptr;
		INV_OBJECT *destP = (INV_OBJECT *)g_invObjects;

		for (int i = 0; i < num; ++i, ++destP, srcP += 12) {
			memmove(destP, srcP, 12);
			destP->attribute = 0;
		}
	} else if (TinselV2) {
		if (g_invFilms == NULL) {
			// First time - allocate memory
			MEM_NODE *node = MemoryAllocFixed(g_numObjects * sizeof(SCNHANDLE));
			assert(node);
			g_invFilms = (SCNHANDLE *)MemoryDeref(node);
			if (g_invFilms == NULL)
				error(NO_MEM, "inventory scripts");
			memset(g_invFilms, 0, g_numObjects * sizeof(SCNHANDLE));
		}


		// Add defined permanent conversation icons
		// and store all the films separately
		int i;
		INV_OBJECT *pio;
		for (i = 0, pio = g_invObjects; i < g_numObjects; i++, pio++) {
			if (pio->attribute & PERMACONV)
				PermaConvIcon(pio->id, pio->attribute & CONVENDITEM);

			g_invFilms[i] = pio->hIconFilm;
		}
	}
}

/**
 * Called from Glitter function 'dec_invw()' - Declare the bits that the
 * inventory windows are constructed from, and special cursors.
 */
extern void setInvWinParts(SCNHANDLE hf) {
#ifdef DEBUG
	const FILM *pfilm;
#endif

	g_hWinParts = hf;

#ifdef DEBUG
	pfilm = (const FILM *)LockMem(hf);
	assert(FROM_32(pfilm->numreels) >= (uint32)(TinselV2 ? T2_HOPEDFORREELS : T1_HOPEDFORREELS)); // not as many reels as expected
#endif
}

/**
 * Called from Glitter function 'dec_flags()' - Declare the language
 * flag films
 */
extern void setFlagFilms(SCNHANDLE hf) {
#ifdef DEBUG
	const FILM *pfilm;
#endif

	g_flagFilm = hf;

#ifdef DEBUG
	pfilm = (const FILM *)LockMem(hf);
	assert(FROM_32(pfilm->numreels) >= HOPEDFORFREELS); // not as many reels as expected
#endif
}

/**
 * Called from Glitter function 'DecCStrings()'
 */
extern void setConfigStrings(SCNHANDLE *tp) {
	memcpy(g_configStrings, tp, sizeof(g_configStrings));
}

/**
 * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
 * - Declare the heading text and dimensions etc.
 */
extern void idec_inv(int num, SCNHANDLE text, int MaxContents,
		int MinWidth, int MinHeight,
		int StartWidth, int StartHeight,
		int MaxWidth, int MaxHeight,
		int startx, int starty, bool moveable) {
	if (MaxWidth > MAXHICONS)
		MaxWidth = MAXHICONS;		// Max window width
	if (MaxHeight > MAXVICONS)
		MaxHeight = MAXVICONS;		// Max window height
	if (MaxContents > MAX_ININV)
		MaxContents = MAX_ININV;	// Max contents

	if (StartWidth > MaxWidth)
		StartWidth = MaxWidth;
	if (StartHeight > MaxHeight)
		StartHeight = MaxHeight;

	g_InventoryState = IDLE_INV;

	g_InvD[num].MaxHicons = MaxWidth;
	g_InvD[num].MinHicons = MinWidth;
	g_InvD[num].MaxVicons = MaxHeight;
	g_InvD[num].MinVicons = MinHeight;

	g_InvD[num].NoofHicons = StartWidth;
	g_InvD[num].NoofVicons = StartHeight;

	memset(g_InvD[num].contents, 0, sizeof(g_InvD[num].contents));
	g_InvD[num].NoofItems = 0;

	g_InvD[num].FirstDisp = 0;

	g_InvD[num].inventoryX = startx;
	g_InvD[num].inventoryY = starty;
	g_InvD[num].otherX = 21;
	g_InvD[num].otherY = 15;

	g_InvD[num].MaxInvObj = MaxContents;

	g_InvD[num].hInvTitle = text;

	if (MaxWidth != MinWidth && MaxHeight != MinHeight)
		g_InvD[num].resizable = true;

	g_InvD[num].bMoveable = moveable;

	g_InvD[num].bMax = false;
}

/**
 * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
 * - Declare the heading text and dimensions etc.
 */
extern void idec_convw(SCNHANDLE text, int MaxContents,
		int MinWidth, int MinHeight,
		int StartWidth, int StartHeight,
		int MaxWidth, int MaxHeight) {
	idec_inv(INV_CONV, text, MaxContents, MinWidth, MinHeight,
			StartWidth, StartHeight, MaxWidth, MaxHeight,
			20, 8, true);
}

/**
 * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
 * - Declare the heading text and dimensions etc.
 */
extern void idec_inv1(SCNHANDLE text, int MaxContents,
		int MinWidth, int MinHeight,
		int StartWidth, int StartHeight,
		int MaxWidth, int MaxHeight) {
	idec_inv(INV_1, text, MaxContents, MinWidth, MinHeight,
			StartWidth, StartHeight, MaxWidth, MaxHeight,
			100, 100, true);
}

/**
 * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
 * - Declare the heading text and dimensions etc.
 */
extern void idec_inv2(SCNHANDLE text, int MaxContents,
		int MinWidth, int MinHeight,
		int StartWidth, int StartHeight,
		int MaxWidth, int MaxHeight) {
	idec_inv(INV_2, text, MaxContents, MinWidth, MinHeight,
			StartWidth, StartHeight, MaxWidth, MaxHeight,
			100, 100, true);
}

/**
 * Called from Glitter function 'GetInvLimit()'
 */
extern int InvGetLimit(int invno) {
	assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported

	return g_InvD[invno].MaxInvObj;
}

/**
 * Called from Glitter function 'SetInvLimit()'
 */
extern void InvSetLimit(int invno, int MaxContents) {
	assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported
	assert(MaxContents >= g_InvD[invno].NoofItems); // can't reduce maximum contents below current contents

	if (MaxContents > MAX_ININV)
		MaxContents = MAX_ININV;	// Max contents

	g_InvD[invno].MaxInvObj = MaxContents;
}

/**
 * Called from Glitter function 'SetInvSize()'
 */
extern void InvSetSize(int invno, int MinWidth, int MinHeight,
		int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) {
	assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported

	if (StartWidth > MaxWidth)
		StartWidth = MaxWidth;
	if (StartHeight > MaxHeight)
		StartHeight = MaxHeight;

	g_InvD[invno].MaxHicons = MaxWidth;
	g_InvD[invno].MinHicons = MinWidth;
	g_InvD[invno].MaxVicons = MaxHeight;
	g_InvD[invno].MinVicons = MinHeight;

	g_InvD[invno].NoofHicons = StartWidth;
	g_InvD[invno].NoofVicons = StartHeight;

	if (MaxWidth != MinWidth && MaxHeight != MinHeight)
		g_InvD[invno].resizable = true;
	else
		g_InvD[invno].resizable = false;

	g_InvD[invno].bMax = false;
}

/**************************************************************************/

extern bool IsTopWindow() {
	return (g_InventoryState == BOGUS_INV);
}

extern bool MenuActive() {
	return (g_InventoryState == ACTIVE_INV && g_ino == INV_CONF);
}

extern bool IsConvWindow() {
	return (g_InventoryState == ACTIVE_INV && g_ino == INV_CONV);
}

} // End of namespace Tinsel