1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  * Glitter library functions.
22  *
23  * In the main called only from PCODE.C
24  * Function names are the same as Glitter code function names.
25  *
26  * To ensure exclusive use of resources and exclusive control responsibilities.
27  */
28 
29 #define BODGE
30 
31 #include "common/coroutines.h"
32 #include "tinsel/actors.h"
33 #include "tinsel/background.h"
34 #include "tinsel/bmv.h"
35 #include "tinsel/config.h"
36 #include "tinsel/cursor.h"
37 #include "tinsel/drives.h"
38 #include "tinsel/dw.h"
39 #include "tinsel/events.h"
40 #include "tinsel/faders.h"
41 #include "tinsel/film.h"
42 #include "tinsel/font.h"
43 #include "tinsel/graphics.h"
44 #include "tinsel/handle.h"
45 #include "tinsel/dialogs.h"
46 #include "tinsel/mareels.h"
47 #include "tinsel/move.h"
48 #include "tinsel/multiobj.h"
49 #include "tinsel/music.h"
50 #include "tinsel/object.h"
51 #include "tinsel/palette.h"
52 #include "tinsel/pcode.h"
53 #include "tinsel/pid.h"
54 #include "tinsel/play.h"
55 #include "tinsel/polygons.h"
56 #include "tinsel/movers.h"
57 #include "tinsel/savescn.h"
58 #include "tinsel/sched.h"
59 #include "tinsel/scn.h"
60 #include "tinsel/scroll.h"
61 #include "tinsel/sound.h"
62 #include "tinsel/strres.h"
63 #include "tinsel/sysvar.h"
64 #include "tinsel/text.h"
65 #include "tinsel/timers.h"		// For ONE_SECOND constant
66 #include "tinsel/tinlib.h"
67 #include "tinsel/tinsel.h"
68 #include "tinsel/token.h"
69 
70 #include "common/textconsole.h"
71 
72 namespace Tinsel {
73 
74 //----------------- EXTERNAL GLOBAL DATA --------------------
75 
76 // In DOS_DW.C
77 extern bool g_bRestart;		// restart flag - set to restart the game
78 extern bool g_bHasRestarted;	// Set after a restart
79 
80 // In PCODE.CPP
81 extern bool g_bNoPause;
82 
83 // In DOS_MAIN.C
84 // TODO/FIXME: From dos_main.c: "Only used on PSX so far"
85 //int clRunMode = 0;
86 
87 //----------------- EXTERNAL FUNCTIONS ---------------------
88 
89 // in PDISPLAY.CPP
90 extern void EnableTags();
91 extern void DisableTags();
92 bool DisableTagsIfEnabled();
93 extern void setshowstring();
94 
95 // in SAVELOAD.CPP
96 extern int NewestSavedGame();
97 
98 // in SCENE.CPP
99 extern void setshowpos();
100 extern int g_sceneCtr;
101 
102 // in TINSEL.CPP
103 extern void SetCdChangeScene(SCNHANDLE hScene);
104 extern void SetHookScene(SCNHANDLE scene, int entrance, int transition);
105 extern void SetNewScene(SCNHANDLE scene, int entrance, int transition);
106 extern void UnHookScene();
107 extern void SuspendHook();
108 extern void UnSuspendHook();
109 
110 #ifdef BODGE
111 // In SCENE.CPP
112 SCNHANDLE GetSceneHandle();
113 #endif
114 
115 //----------------- LOCAL DEFINES --------------------
116 
117 #define JAP_TEXT_TIME	(2*ONE_SECOND)
118 
119 /*----------------------------------------------------------------------*\
120 |*                      Library Procedure and Function codes            *|
121 \*----------------------------------------------------------------------*/
122 
123 enum MASTER_LIB_CODES {
124 	ACTORATTR, ACTORBRIGHTNESS, ACTORDIRECTION, ACTORPALETTE, ACTORPRIORITY, ACTORREF,
125 	ACTORRGB, ACTORSCALE, ACTORSON, ACTORXPOS, ACTORYPOS, ADDHIGHLIGHT,
126 	ADDINV, ADDINV1, ADDINV2, ADDOPENINV, ADDTOPIC, AUXSCALE, BACKGROUND, BLOCKING,
127 	CALLACTOR, CALLGLOBALPROCESS, CALLOBJECT, CALLPROCESS, CALLSCENE, CALLTAG,
128 	CAMERA, CDCHANGESCENE, CDDOCHANGE, CDENDACTOR, CDLOAD, CDPLAY, CLEARHOOKSCENE,
129 	CLOSEINVENTORY, CONTROL, CONVERSATION, CONVTOPIC, CURSOR, CURSORXPOS, CURSORYPOS,
130 	CUTSCENE, DECCONVW, DECCSTRINGS, DECCURSOR, DECFLAGS, DECINV1, DECINV2, DECINVW,
131 	DECLARELANGUAGE, DECLEAD, DECSCALE, DECTAGFONT, DECTALKFONT, DELICON,
132 	DELINV, DELTOPIC, DIMMUSIC, DROP, DROPEVERYTHING, DROPOUT, EFFECTACTOR, ENABLEMENU,
133 	ENDACTOR, ESCAPE, ESCAPEOFF, ESCAPEON, EVENT, FACETAG, FADEIN, FADEMIDI,
134 	FADEOUT, FRAMEGRAB, FREEZECURSOR, GETINVLIMIT, GHOST, GLOBALVAR, GRABMOVIE, HAILSCENE,
135 	HASRESTARTED, HAVE, HELDOBJECT, HIDEACTOR, HIDEBLOCK, HIDEEFFECT, HIDEPATH,
136 	HIDEREFER, HIDETAG, HOLD, HOOKSCENE, IDLETIME, ININVENTORY, INSTANTSCROLL, INVDEPICT,
137 	INVENTORY, INVPLAY, INWHICHINV, KILLACTOR, KILLBLOCK, KILLEXIT, KILLGLOBALPROCESS,
138 	KILLPROCESS, KILLTAG, LOCALVAR, MOVECURSOR, MOVETAG, MOVETAGTO, NEWSCENE,
139 	NOBLOCKING, NOPAUSE, NOSCROLL, OBJECTHELD, OFFSET, OTHEROBJECT, PAUSE, PLAY, PLAYMIDI,
140 	PLAYMOVIE, PLAYMUSIC, PLAYRTF, PLAYSAMPLE, POINTACTOR, POINTTAG, POSTACTOR, POSTGLOBALPROCESS,
141 	POSTOBJECT, POSTPROCESS, POSTTAG, PREPARESCENE, PRINT, PRINTCURSOR, PRINTOBJ, PRINTTAG,
142 	QUITGAME, RANDOM, RESETIDLETIME, RESTARTGAME, RESTORESCENE, RESTORE_CUT,
143 	RESUMELASTGAME, RUNMODE, SAMPLEPLAYING, SAVESCENE, SAY, SAYAT, SCALINGREELS,
144 	SCANICON, SCREENXPOS, SCREENYPOS, SCROLL, SCROLLPARAMETERS, SENDACTOR, SENDGLOBALPROCESS,
145 	SENDOBJECT, SENDPROCESS, SENDTAG, SETACTOR, SETBLOCK, SETBRIGHTNESS, SETEXIT, SETINVLIMIT,
146 	SETINVSIZE, SETLANGUAGE, SETPALETTE, SETSYSTEMREEL, SETSYSTEMSTRING, SETSYSTEMVAR,
147 	SETTAG, SETTIMER, SHELL, SHOWACTOR, SHOWBLOCK, SHOWEFFECT, SHOWMENU, SHOWPATH,
148 	SHOWPOS, SHOWREFER, SHOWSTRING, SHOWTAG, SPLAY, STAND, STANDTAG, STARTGLOBALPROCESS,
149 	STARTPROCESS, STARTTIMER, STOPMIDI, STOPSAMPLE, STOPWALK, SUBTITLES, SWALK, SWALKZ,
150 	SYSTEMVAR, TAGACTOR, TAGTAGXPOS, TAGTAGYPOS, TAGWALKXPOS, TAGWALKYPOS, TALK, TALKAT,
151 	TALKATS, TALKATTR, TALKPALETTEINDEX, TALKRGB, TALKVIA, TEMPTAGFONT, TEMPTALKFONT,
152 	THISOBJECT, THISTAG, TIMER, TOPIC, TOPPLAY, TOPWINDOW, TRANSLUCENTINDEX,
153 	TRYPLAYSAMPLE, UNDIMMUSIC, UNHOOKSCENE, UNTAGACTOR, VIBRATE, WAITFRAME, WAITKEY,
154 	WAITSCROLL, WAITTIME, WALK, WALKED, WALKEDPOLY, WALKEDTAG, WALKINGACTOR, WALKPOLY,
155 	WALKTAG, WALKXPOS, WALKYPOS, WHICHCD, WHICHINVENTORY, ZZZZZZ, DEC3D, DECINVMAIN,
156 	ADDNOTEBOOK, ADDINV3, ADDCONV, SET3DTEXTURE, FADEMUSIC, VOICEOVER, SETVIEW, HIGHEST_LIBCODE
157 };
158 
159 static const MASTER_LIB_CODES DW1DEMO_CODES[] = {
160 	ACTORREF, ACTORXPOS, ACTORYPOS, ADDTOPIC, ADDINV1, ADDINV2, AUXSCALE, BACKGROUND,
161 	CAMERA, CONTROL, CONVERSATION, CONVTOPIC, HIGHEST_LIBCODE, CURSORXPOS, CURSORYPOS,
162 	DECCONVW, DECCURSOR, DECTAGFONT, DECINVW, DECINV1, DECINV2, DECLEAD, DELICON,
163 	DELINV, EVENT, HIGHEST_LIBCODE, HELDOBJECT, HIDEACTOR, ININVENTORY, HIGHEST_LIBCODE,
164 	INVENTORY, HIGHEST_LIBCODE, KILLACTOR, KILLBLOCK, KILLTAG, SCREENXPOS,
165 	HIGHEST_LIBCODE, MOVECURSOR, NEWSCENE, NOSCROLL, OBJECTHELD, OFFSET, HIGHEST_LIBCODE,
166 	PLAY, PLAYSAMPLE, PREPARESCENE, PRINT, PRINTOBJ, PRINTTAG, RESTORESCENE, SAVESCENE,
167 	SCANICON, SCROLL, SETACTOR, SETBLOCK, HIGHEST_LIBCODE, SETTAG, SETTIMER, SHOWPOS,
168 	SPLAY, STAND, STANDTAG, STOPWALK, HIGHEST_LIBCODE, SWALK, TAGACTOR, TALK,
169 	SCREENYPOS, UNTAGACTOR, VIBRATE, WAITKEY, WAITTIME, WALK, WALKINGACTOR, WALKPOLY,
170 	WALKTAG, RANDOM, TIMER
171 };
172 
173 static const MASTER_LIB_CODES DW1_CODES[] = {
174 	ACTORATTR, ACTORDIRECTION, ACTORREF, ACTORSCALE, ACTORXPOS,
175 	ACTORYPOS, ADDTOPIC, ADDINV1, ADDINV2, ADDOPENINV, AUXSCALE,
176 	BACKGROUND, CAMERA, CLOSEINVENTORY, CONTROL, CONVERSATION,
177 	CONVTOPIC, CURSORXPOS, CURSORYPOS, DECCONVW, DECCURSOR,
178 	DECINV1, DECINV2, DECINVW, DECLEAD, DECTAGFONT,
179 	DECTALKFONT, DELICON, DELINV, EFFECTACTOR, ESCAPE, EVENT,
180 	GETINVLIMIT, HELDOBJECT, HIDEACTOR, ININVENTORY, INVDEPICT,
181 	INVENTORY, KILLACTOR, KILLBLOCK, KILLEXIT, KILLTAG, SCREENXPOS,
182 	MOVECURSOR, NEWSCENE, NOSCROLL, OBJECTHELD, OFFSET, PAUSE,
183 	PLAY, PLAYMIDI, PLAYSAMPLE, PREPARESCENE, PRINT, PRINTOBJ,
184 	PRINTTAG, RANDOM, RESTORESCENE, SAVESCENE, SCALINGREELS,
185 	SCANICON, SCROLL, SETACTOR, SETBLOCK, SETEXIT, SETINVLIMIT,
186 	SETPALETTE, SETTAG, SETTIMER, SHOWPOS, SHOWSTRING, SPLAY,
187 	STAND, STANDTAG, STOPWALK, SWALK, TAGACTOR, TALK, TALKATTR, TIMER,
188 	SCREENYPOS, TOPPLAY, TOPWINDOW, UNTAGACTOR, VIBRATE, WAITKEY,
189 	WAITTIME, WALK, WALKED, WALKINGACTOR, WALKPOLY, WALKTAG,
190 	WHICHINVENTORY, ACTORSON, CUTSCENE, HOOKSCENE, IDLETIME,
191 	RESETIDLETIME, TALKAT, UNHOOKSCENE, WAITFRAME,	DECCSTRINGS,
192 	STOPMIDI, STOPSAMPLE, TALKATS, DECFLAGS, FADEMIDI,
193 	CLEARHOOKSCENE, SETINVSIZE, INWHICHINV, NOBLOCKING,
194 	SAMPLEPLAYING, TRYPLAYSAMPLE, ENABLEMENU, RESTARTGAME, QUITGAME,
195 	FRAMEGRAB, PLAYRTF, CDPLAY, CDLOAD, HASRESTARTED, RESTORE_CUT,
196 	RUNMODE, SUBTITLES, SETLANGUAGE,
197 	HIGHEST_LIBCODE
198 };
199 
200 static const MASTER_LIB_CODES DW2DEMO_CODES[] = {
201 	ACTORBRIGHTNESS, ACTORDIRECTION, ACTORPALETTE, ACTORPRIORITY,
202 	ACTORREF, ACTORRGB, ACTORSCALE, ACTORXPOS, ACTORYPOS,
203 	ADDHIGHLIGHT, ADDINV, ADDINV1, ADDINV2, ADDOPENINV, ADDTOPIC,
204 	BACKGROUND, CALLACTOR, CALLGLOBALPROCESS, CALLOBJECT,
205 	CALLPROCESS, CALLSCENE, CALLTAG, CAMERA, CDCHANGESCENE,
206 	CDDOCHANGE, CDLOAD, CDPLAY, CLEARHOOKSCENE, CLOSEINVENTORY,
207 	CONTROL, CONVERSATION, CURSOR, CURSORXPOS, CURSORYPOS,
208 	DECCONVW, DECCURSOR, DECFLAGS, DECINV1, DECINV2, DECINVW,
209 	DECLEAD, DECSCALE, DECTAGFONT, DECTALKFONT, DELTOPIC,
210 	DIMMUSIC, DROP, DROPOUT, EFFECTACTOR, ENABLEMENU, ENDACTOR,
211 	ESCAPEOFF, ESCAPEON, EVENT, FACETAG, FADEIN, FADEOUT, FRAMEGRAB,
212 	FREEZECURSOR, GETINVLIMIT, GHOST, GLOBALVAR, HASRESTARTED,
213 	HAVE, HELDOBJECT, HIDEACTOR, HIDEBLOCK, HIDEEFFECT, HIDEPATH,
214 	HIDEREFER, HIDETAG, HOLD, HOOKSCENE, IDLETIME, INSTANTSCROLL,
215 	INVENTORY, INVPLAY, INWHICHINV, KILLACTOR, KILLGLOBALPROCESS,
216 	KILLPROCESS, LOCALVAR, MOVECURSOR, MOVETAG, MOVETAGTO, NEWSCENE,
217 	NOBLOCKING, NOPAUSE, NOSCROLL, OFFSET, OTHEROBJECT, PAUSE, PLAY,
218 	PLAYMUSIC, PLAYRTF, PLAYSAMPLE, POINTACTOR, POINTTAG, POSTACTOR,
219 	POSTGLOBALPROCESS, POSTOBJECT, POSTPROCESS, POSTTAG, PRINT,
220 	PRINTCURSOR, PRINTOBJ, PRINTTAG, QUITGAME, RANDOM, RESETIDLETIME,
221 	RESTARTGAME, RESTORESCENE, RUNMODE, SAVESCENE, SAY, SAYAT,
222 	SCALINGREELS, SCREENXPOS, SCREENYPOS, SCROLL, SCROLLPARAMETERS,
223 	SENDACTOR, SENDGLOBALPROCESS, SENDOBJECT, SENDPROCESS, SENDTAG,
224 	SETBRIGHTNESS, SETINVLIMIT, SETINVSIZE, SETLANGUAGE, SETPALETTE,
225 	SETSYSTEMSTRING, SETSYSTEMVAR, SHELL, SHOWACTOR, SHOWBLOCK,
226 	SHOWEFFECT, SHOWPATH, SHOWREFER, SHOWTAG, STAND, STANDTAG,
227 	STARTGLOBALPROCESS, STARTPROCESS, STARTTIMER, STOPWALK, SUBTITLES,
228 	SWALK, SYSTEMVAR, TAGTAGXPOS, TAGTAGYPOS, TAGWALKXPOS, TAGWALKYPOS,
229 	TALK, TALKAT, TALKPALETTEINDEX, TALKRGB, TALKVIA, THISOBJECT,
230 	THISTAG, TIMER, TOPIC, TOPPLAY, TOPWINDOW, TRANSLUCENTINDEX,
231 	UNDIMMUSIC, UNHOOKSCENE, WAITFRAME, WAITKEY, WAITSCROLL, WAITTIME,
232 	WALK, WALKED, WALKEDPOLY, WALKEDTAG, WALKINGACTOR, WALKPOLY,
233 	WALKTAG, WALKXPOS, WALKYPOS, WHICHCD, WHICHINVENTORY,
234 	HIGHEST_LIBCODE
235 };
236 
237 static const MASTER_LIB_CODES DW2_CODES[] = {
238 	ACTORBRIGHTNESS, ACTORDIRECTION, ACTORPALETTE, ACTORPRIORITY,
239 	ACTORREF, ACTORRGB, ACTORSCALE, ACTORXPOS, ACTORYPOS,
240 	ADDHIGHLIGHT, ADDINV, ADDINV1, ADDINV2, ADDOPENINV, ADDTOPIC,
241 	BACKGROUND, CALLACTOR, CALLGLOBALPROCESS, CALLOBJECT,
242 	CALLPROCESS, CALLSCENE, CALLTAG, CAMERA, CDCHANGESCENE,
243 	CDDOCHANGE, CDLOAD, CDPLAY, CLEARHOOKSCENE, CLOSEINVENTORY,
244 	CONTROL, CONVERSATION, CURSOR, CURSORXPOS, CURSORYPOS,
245 	DECCONVW, DECCURSOR, DECFLAGS, DECINV1, DECINV2, DECINVW,
246 	DECLEAD, DECSCALE, DECTAGFONT, DECTALKFONT, DELTOPIC,
247 	DIMMUSIC, DROP, DROPOUT, EFFECTACTOR, ENABLEMENU, ENDACTOR,
248 	ESCAPEOFF, ESCAPEON, EVENT, FACETAG, FADEIN, FADEOUT, FRAMEGRAB,
249 	FREEZECURSOR, GETINVLIMIT, GHOST, GLOBALVAR, GRABMOVIE,
250 	HASRESTARTED, HAVE, HELDOBJECT, HIDEACTOR, HIDEBLOCK, HIDEEFFECT,
251 	HIDEPATH, HIDEREFER, HIDETAG, HOLD, HOOKSCENE, IDLETIME,
252 	INSTANTSCROLL, INVENTORY, INVPLAY, INWHICHINV, KILLACTOR,
253 	KILLGLOBALPROCESS, KILLPROCESS, LOCALVAR, MOVECURSOR, MOVETAG,
254 	MOVETAGTO, NEWSCENE, NOBLOCKING, NOPAUSE, NOSCROLL, OFFSET,
255 	OTHEROBJECT, PAUSE, PLAY, PLAYMUSIC, PLAYRTF, PLAYSAMPLE,
256 	POINTACTOR, POINTTAG, POSTACTOR, POSTGLOBALPROCESS, POSTOBJECT,
257 	POSTPROCESS, POSTTAG, PRINT, PRINTCURSOR, PRINTOBJ, PRINTTAG,
258 	QUITGAME, RANDOM, RESETIDLETIME, RESTARTGAME, RESTORESCENE,
259 	RUNMODE, SAVESCENE, SAY, SAYAT, SCALINGREELS, SCREENXPOS,
260 	SCREENYPOS, SCROLL, SCROLLPARAMETERS, SENDACTOR, SENDGLOBALPROCESS,
261 	SENDOBJECT, SENDPROCESS, SENDTAG, SETBRIGHTNESS, SETINVLIMIT,
262 	SETINVSIZE, SETLANGUAGE, SETPALETTE, SETSYSTEMSTRING, SETSYSTEMVAR,
263 	SHELL, SHOWACTOR, SHOWBLOCK, SHOWEFFECT, SHOWPATH, SHOWREFER,
264 	SHOWTAG, STAND, STANDTAG, STARTGLOBALPROCESS, STARTPROCESS,
265 	STARTTIMER, STOPWALK, SUBTITLES, SWALK, SYSTEMVAR, TAGTAGXPOS,
266 	TAGTAGYPOS, TAGWALKXPOS, TAGWALKYPOS, TALK, TALKAT, TALKPALETTEINDEX,
267 	TALKRGB, TALKVIA, THISOBJECT, THISTAG, TIMER, TOPIC, TOPPLAY,
268 	TOPWINDOW, TRANSLUCENTINDEX, UNDIMMUSIC, UNHOOKSCENE, WAITFRAME,
269 	WAITKEY, WAITSCROLL, WAITTIME, WALK, WALKED, WALKEDPOLY, WALKEDTAG,
270 	WALKINGACTOR, WALKPOLY, WALKTAG, WALKXPOS, WALKYPOS, WHICHCD,
271 	WHICHINVENTORY, ZZZZZZ, SWALKZ, DROPEVERYTHING, BLOCKING, STOPSAMPLE,
272 	CDENDACTOR, DECLARELANGUAGE, RESUMELASTGAME, SHOWMENU, TEMPTALKFONT,
273 	TEMPTAGFONT, PLAYMOVIE, HAILSCENE, SETSYSTEMREEL,
274 	HIGHEST_LIBCODE
275 };
276 
277 //----------------- GLOBAL GLOBAL DATA --------------------
278 
279 // These vars are reset upon engine destruction
280 
281 bool g_bEnableMenu;
282 
283 static bool g_bInstantScroll = false;
284 static bool g_bEscapedCdPlay = false;
285 
286 //----------------- LOCAL GLOBAL DATA --------------------
287 
288 // These vars are reset upon engine destruction
289 
290 // Saved cursor co-ordinates for control(on) to restore cursor position
291 // as it was at control(off).
292 // They are global so that MoveCursor(..) has a net effect if it
293 // precedes control(on).
294 static int g_controlX = 0, g_controlY = 0;
295 
296 static int g_offtype = 0;			// used by Control()
297 static uint32 g_lastValue = 0;	// used by RandomFn()
298 static int g_scrollNumber = 0;	// used by scroll()
299 
300 static bool g_bNotPointedRunning = false;	// Used in Printobj and PrintObjPointed
301 
302 //----------------- FORWARD REFERENCES --------------------
303 
304 static int HeldObject();
305 static void PostTag(CORO_PARAM, int tagno, TINSEL_EVENT event, HPOLYGON hp, int myEscape);
306 void ResetIdleTime();
307 static void SendTag(CORO_PARAM, int tagno, TINSEL_EVENT event, HPOLYGON hp, int myEscape, bool *result);
308 static void StandTag(int actor, HPOLYGON hp);
309 void StopMidiFn();
310 void StopSample(int sample = -1);
311 static void StopWalk(int actor);
312 static void WaitScroll(CORO_PARAM, int myescEvent);
313 void Walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool igPath,
314 		  int zOverride, bool escOn, int myescTime);
315 
316 //----------------- SUPPORT FUNCTIONS --------------------
317 
ResetVarsTinlib()318 void ResetVarsTinlib() {
319 	g_bEnableMenu = false;
320 
321 	g_bInstantScroll = false;
322 	g_bEscapedCdPlay = false;
323 	g_controlX = 0;
324 	g_controlY = 0;
325 
326 	g_offtype = 0;
327 	g_lastValue = 0;
328 	g_scrollNumber = 0;
329 
330 	g_bNotPointedRunning = false;
331 }
332 
333 /**
334  * For ScrollScreen() and Offset(), work out top left for a
335  * given screen position.
336  */
DecodeExtreme(EXTREME extreme,int * px,int * py)337 static void DecodeExtreme(EXTREME extreme, int *px, int *py) {
338 	int	Loffset, Toffset;
339 
340 	_vm->_bg->PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
341 
342 	switch (extreme) {
343 	case EX_BOTTOM:
344 		*px = Loffset;
345 		*py = _vm->_bg->BgHeight() - SCREEN_HEIGHT;
346 		break;
347 	case EX_BOTTOMLEFT:
348 		*px = 0;
349 		*py = _vm->_bg->BgHeight() - SCREEN_HEIGHT;
350 		break;
351 	case EX_BOTTOMRIGHT:
352 		*px = _vm->_bg->BgWidth() - SCREEN_WIDTH;
353 		*py = _vm->_bg->BgHeight() - SCREEN_HEIGHT;
354 		break;
355 	case EX_LEFT:
356 		*px = 0;
357 		*py = Toffset;
358 		break;
359 	case EX_RIGHT:
360 		*px = _vm->_bg->BgWidth() - SCREEN_WIDTH;
361 		*py = Toffset;
362 		break;
363 	case EX_TOP:
364 		*px = Loffset;
365 		*py = 0;
366 		break;
367 	case EX_TOPLEFT:
368 		*px = *py = 0;
369 		break;
370 	case EX_TOPRIGHT:
371 		*px = _vm->_bg->BgWidth() - SCREEN_WIDTH;
372 		*py = 0;
373 		break;
374 	default:
375 		break;
376 	}
377 }
378 
KillSelf(CORO_PARAM)379 static void KillSelf(CORO_PARAM) {
380 	CORO_BEGIN_CONTEXT;
381 	CORO_END_CONTEXT(_ctx);
382 
383 	CORO_BEGIN_CODE(_ctx);
384 
385 	CORO_KILL_SELF();
386 
387 	CORO_END_CODE;
388 }
389 
390 struct SCROLL_MONITOR {
391 	int	x;
392 	int	y;
393 	int	thisScroll;
394 	int	myEscape;
395 };
396 typedef SCROLL_MONITOR *PSCROLL_MONITOR;
397 
398 /**
399  * Monitor a scrolling, allowing Escape to interrupt it
400  */
ScrollMonitorProcess(CORO_PARAM,const void * param)401 static void ScrollMonitorProcess(CORO_PARAM, const void *param) {
402 	int		Loffset, Toffset;
403 	const SCROLL_MONITOR *psm = (const SCROLL_MONITOR *)param;
404 
405 	// COROUTINE
406 	CORO_BEGIN_CONTEXT;
407 	CORO_END_CONTEXT(_ctx);
408 
409 	CORO_BEGIN_CODE(_ctx);
410 
411 	do {
412 		CORO_SLEEP(1);
413 
414 		// give up if have been superseded
415 		if (psm->thisScroll != g_scrollNumber)
416 			break;
417 
418 		// If ESCAPE is pressed...
419 		if (psm->myEscape != GetEscEvents()) {
420 			// Instant completion!
421 			Offset(EX_USEXY, psm->x, psm->y);
422 			break;
423 		}
424 
425 		_vm->_bg->PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
426 
427 	} while (Loffset != psm->x || Toffset != psm->y);
428 
429 	CORO_END_CODE;
430 }
431 
432 /**
433  * NOT A LIBRARY FUNCTION
434  *
435  * Poke supplied color into the DAC queue.
436  */
SetTextPal(COLORREF col)437 void SetTextPal(COLORREF col) {
438 	SetTalkColorRef(col);
439 	UpdateDACqueue(TalkColor(), col);
440 }
441 
442 /**
443  * Work out a time depending on length of string and
444  * subtitle speed modification.
445  */
TextTime(char * pTstring)446 static int TextTime(char *pTstring) {
447 	if (_vm->_config->isJapanMode())
448 		return JAP_TEXT_TIME;
449 	else if (!_vm->_config->_textSpeed)
450 		return strlen(pTstring) + ONE_SECOND;
451 	else
452 		return strlen(pTstring) + ONE_SECOND + (_vm->_config->_textSpeed * 5 * ONE_SECOND) / 100;
453 }
454 
455 /**
456  * KeepOnScreen
457  */
KeepOnScreen(POBJECT pText,int * pTextX,int * pTextY)458 void KeepOnScreen(POBJECT pText, int *pTextX, int *pTextY) {
459 	int	shift;
460 
461 	// Not off the left
462 	shift = MultiLeftmost(pText);
463 	if (shift < 0) {
464 		MultiMoveRelXY(pText, - shift, 0);
465 		*pTextX -= shift;
466 	}
467 
468 	// Not off the right
469 	shift = MultiRightmost(pText);
470 	if (shift > SCREEN_WIDTH) {
471 		MultiMoveRelXY(pText, SCREEN_WIDTH - shift, 0);
472 		*pTextX += SCREEN_WIDTH - shift;
473 	}
474 
475 	// Not off the top
476 	shift = MultiHighest(pText);
477 	if (shift < 0) {
478 		MultiMoveRelXY(pText, 0, - shift);
479 		*pTextY -= shift;
480 	}
481 
482 	// Not off the bottom
483 	shift = MultiLowest(pText);
484 	if (shift > SCREEN_BOX_HEIGHT2) {
485 		MultiMoveRelXY(pText, 0, SCREEN_BOX_HEIGHT2 - shift);
486 		*pTextX += SCREEN_WIDTH - shift;
487 	}
488 }
489 
490 /**
491  * Waits until the specified process is finished
492  */
FinishWaiting(CORO_PARAM,const INT_CONTEXT * pic,bool * result=NULL)493 static void FinishWaiting(CORO_PARAM, const INT_CONTEXT *pic, bool *result = NULL) {
494 	CORO_BEGIN_CONTEXT;
495 	CORO_END_CONTEXT(_ctx);
496 
497 	CORO_BEGIN_CODE(_ctx);
498 
499 	while (pic->resumeCode == RES_WAITING)
500 		CORO_SLEEP(1);
501 
502 	if (result)
503 		*result = pic->resumeCode == RES_FINISHED;
504 	CORO_END_CODE;
505 }
506 
TinGetVersion(WHICH_VER which,char * buffer,int length)507 void TinGetVersion(WHICH_VER which, char *buffer, int length) {
508 
509 	if (length > VER_LEN)
510 		length = VER_LEN;
511 
512 	char *cptr = (char *)FindChunk(MASTER_SCNHANDLE, CHUNK_TIME_STAMPS);
513 
514 	switch (which)	{
515 	case VER_GLITTER:
516 		memcpy(buffer, cptr, length);
517 		break;
518 
519 	case VER_COMPILE:
520 		memcpy(buffer, cptr + VER_LEN, length);
521 		break;
522 
523 	default:
524 		break;
525 	}
526 }
527 
528 /********************************************************************\
529 |*****			Library functions								*****|
530 \********************************************************************/
531 
532 /**
533  * Set actor's attributes.
534  * - currently only the text color.
535  */
ActorAttr(int actor,int r1,int g1,int b1)536 static void ActorAttr(int actor, int r1, int g1, int b1) {
537 	_vm->_actor->storeActorAttr(actor, r1, g1, b1);
538 }
539 
540 /**
541  * Behave as if actor has walked into a polygon with given brughtness.
542  */
ActorBrightness(int actor,int brightness)543 void ActorBrightness(int actor, int brightness) {
544 	PMOVER pMover = GetMover(actor);
545 
546 	assert(pMover != NULL);
547 	assert(brightness >= 0 && brightness <= 10);
548 
549 	MoverBrightness(pMover, brightness);
550 }
551 
552 /**
553  * Return a moving actor's current direction.
554  */
ActorDirection(int actor)555 static int ActorDirection(int actor) {
556 	PMOVER pMover = GetMover(actor);
557 	assert(pMover);
558 
559 	return (int)GetMoverDirection(pMover);
560 }
561 
562 /**
563  * Set actor's palette details for path brightnesses
564  */
ActorPalette(int actor,int startColor,int length)565 void ActorPalette(int actor, int startColor, int length) {
566 	PMOVER pMover = GetMover(actor);
567 	assert(pMover);
568 
569 	StoreMoverPalette(pMover, startColor, length);
570 }
571 
572 /**
573  * Set actor's Z-factor.
574  */
ActorPriority(int actor,int zFactor)575 static void ActorPriority(int actor, int zFactor) {
576 	_vm->_actor->SetActorZfactor(actor, zFactor);
577 }
578 
579 /**
580  * Set actor's text color.
581  */
ActorRGB(int actor,COLORREF color)582 static void ActorRGB(int actor, COLORREF color) {
583 	_vm->_actor->SetActorRGB(actor, color);
584 }
585 
586 /**
587  * Return the actor's scale.
588  */
ActorScale(int actor)589 static int ActorScale(int actor) {
590 	PMOVER pMover = GetMover(actor);
591 	assert(pMover);
592 
593 	return (int)GetMoverScale(pMover);
594 }
595 
596 /**
597  * Returns the x or y position of an actor.
598  */
ActorPos(int xory,int actor)599 static int ActorPos(int xory, int actor) {
600 	int x, y;
601 
602 	_vm->_actor->GetActorPos(actor, &x, &y);
603 	return (xory == ACTORXPOS) ? x : y;
604 }
605 
606 /**
607  * Make all actors alive at the start of each scene.
608  */
ActorsOn()609 static void ActorsOn() {
610 	_vm->_actor->SetActorsOn();
611 }
612 
613 /**
614  * Adds an icon to the conversation window.
615  */
AddTopic(int icon)616 static void AddTopic(int icon) {
617 	_vm->_dialogs->AddToInventory(INV_CONV, icon, false);
618 }
619 
620 /**
621  * Place the object in inventory 1 or 2.
622  */
AddInv(int invno,int object)623 static void AddInv(int invno, int object) {
624 	// illegal inventory number
625 	assert(invno == INV_1 || invno == INV_2 || invno == INV_OPEN || invno == INV_DEFAULT);
626 
627 	_vm->_dialogs->AddToInventory(invno, object, false);
628 }
629 
630 /**
631  * Define an actor's walk and stand reels for an auxilliary scale.
632  */
AuxScale(int actor,int scale,SCNHANDLE * rp)633 static void AuxScale(int actor, int scale, SCNHANDLE *rp) {
634 	PMOVER pMover = GetMover(actor);
635 	assert(pMover);
636 
637 	int j;
638 	for (j = 0; j < 4; ++j)
639 		pMover->walkReels[scale-1][j] = *rp++;
640 	for (j = 0; j < 4; ++j)
641 		pMover->standReels[scale-1][j] = *rp++;
642 	for (j = 0; j < 4; ++j)
643 		pMover->talkReels[scale-1][j] = *rp++;
644 }
645 
646 /**
647  * Defines the background image for a scene.
648  */
startBackground(CORO_PARAM,SCNHANDLE bfilm)649 static void startBackground(CORO_PARAM, SCNHANDLE bfilm) {
650 	_vm->_bg->StartupBackground(coroParam, bfilm);
651 }
652 
653 /**
654  * Disable dynamic blocking for current scene.
655  */
Blocking(bool onOrOff)656 void Blocking(bool onOrOff) {
657 	SetSysVar(ISV_NO_BLOCKING, !onOrOff);
658 }
659 
660 /**
661  * Sets focus of the scroll process.
662  */
Camera(int actor)663 static void Camera(int actor) {
664 	_vm->_scroll->ScrollFocus(actor);
665 }
666 
667 /**
668  * Sets the CD Change Scene
669  */
670 
CdChangeScene(SCNHANDLE hScene)671 static void CdChangeScene(SCNHANDLE hScene) {
672 	SetCdChangeScene(hScene);
673 }
674 
675 /**
676  * CdDoChange
677  */
CdDoChange(CORO_PARAM)678 void CdDoChange(CORO_PARAM) {
679 	CORO_BEGIN_CONTEXT;
680 	CORO_END_CONTEXT(_ctx);
681 
682 	CORO_BEGIN_CODE(_ctx);
683 
684 	if (!GotoCD())
685 		return;
686 
687 	CORO_INVOKE_0(CdCD);
688 
689 	CdHasChanged();
690 
691 	CORO_END_CODE;
692 }
693 
694 /**
695  * CdEndActor("actor")
696  */
CdEndActor(int actor,int myEscape)697 void CdEndActor(int	actor, int	myEscape) {
698 	PMOVER	pMover;			// for if it's a moving actor
699 
700 	// Only do it if escaped!
701 	if (myEscape && myEscape != GetEscEvents()) {
702 		// End current graphic
703 		_vm->_actor->dwEndActor(actor);
704 
705 		// un-hide movers
706 		pMover = GetMover(actor);
707 		if (pMover)
708 			UnHideMover(pMover);
709 	}
710 }
711 
712 /**
713  * A CDPLAY() is imminent.
714  */
CDload(SCNHANDLE start,SCNHANDLE next,int myEscape)715 static void CDload(SCNHANDLE start, SCNHANDLE next, int myEscape) {
716 	assert(start && next && start != next); // cdload() fault
717 
718 	if (TinselV2) {
719 		if (myEscape && myEscape != GetEscEvents()) {
720 			g_bEscapedCdPlay = true;
721 			return;
722 		}
723 
724 		_vm->_handle->LoadExtraGraphData(start, next);
725 	}
726 }
727 
728 /**
729  * Clear the hooked scene (if any)
730  */
ClearHookScene()731 static void ClearHookScene() {
732 	SetHookScene(0, 0, TRANS_DEF);
733 }
734 
735 /**
736  * Guess what.
737  */
CloseInventory()738 static void CloseInventory() {
739 	_vm->_dialogs->KillInventory();
740 }
741 
742 /**
743  * Turn off cursor and take control from player - and variations on the	 theme.
744  *  OR Restore cursor and return control to the player.
745  */
Control(int param)746 void Control(int param) {
747 	if (TinselV2) {
748 		if (param)
749 			ControlOn();
750 		else {
751 			ControlOff();
752 
753 			switch (_vm->_dialogs->WhichInventoryOpen()) {
754 			case INV_1:
755 			case INV_2:
756 			case INV_MENU:
757 				_vm->_dialogs->KillInventory();
758 				break;
759 			default:
760 				break;
761 			}
762 		}
763 
764 		return;
765 	}
766 
767 	// Tinsel 1 handling code
768 	g_bEnableMenu = false;
769 
770 	switch (param) {
771 	case CONTROL_STARTOFF:
772 		GetControlToken();	// Take control
773 		DisableTags();			// Switch off tags
774 		_vm->_cursor->DwHideCursor(); // Blank out cursor
775 		g_offtype = param;
776 		break;
777 
778 	case CONTROL_OFF:
779 	case CONTROL_OFFV:
780 	case CONTROL_OFFV2:
781 		if (TestToken(TOKEN_CONTROL)) {
782 			GetControlToken();	// Take control
783 
784 			DisableTags();			// Switch off tags
785 			_vm->_cursor->GetCursorXYNoWait(&g_controlX, &g_controlY, true); // Store cursor position
786 
787 			// There may be a button timing out
788 			GetToken(TOKEN_LEFT_BUT);
789 			FreeToken(TOKEN_LEFT_BUT);
790 		}
791 
792 		if (g_offtype == CONTROL_STARTOFF)
793 			_vm->_cursor->GetCursorXYNoWait(&g_controlX, &g_controlY, true); // Store cursor position
794 
795 		g_offtype = param;
796 
797 		if (param == CONTROL_OFF)
798 			_vm->_cursor->DwHideCursor(); // Blank out cursor
799 		else if (param == CONTROL_OFFV) {
800 			_vm->_cursor->UnHideCursor();
801 			_vm->_cursor->FreezeCursor();
802 		} else if (param == CONTROL_OFFV2) {
803 			_vm->_cursor->UnHideCursor();
804 		}
805 		break;
806 
807 	case CONTROL_ON:
808 		if (g_offtype != CONTROL_OFFV2 && g_offtype != CONTROL_STARTOFF)
809 			_vm->_cursor->SetCursorXY(g_controlX, g_controlY); // ... where it was
810 
811 		FreeControlToken();	// Release control
812 
813 		if (!_vm->_dialogs->InventoryActive())
814 			EnableTags();		// Tags back on
815 
816 		_vm->_cursor->RestoreMainCursor(); // Re-instate cursor...
817 		break;
818 
819 	default:
820 		break;
821 	}
822 }
823 
824 /**
825  * Open or close the conversation window.
826  */
Conversation(CORO_PARAM,int fn,HPOLYGON hp,int actor,bool escOn,int myEscape)827 static void Conversation(CORO_PARAM, int fn, HPOLYGON hp, int actor, bool escOn, int myEscape) {
828 	assert(hp != NOPOLY); // conversation() must (currently) be called from a polygon code block
829 	CORO_BEGIN_CONTEXT;
830 	CORO_END_CONTEXT(_ctx);
831 
832 	CORO_BEGIN_CODE(_ctx);
833 
834 	if (fn == CONV_END) {
835 		// Close down conversation
836 		_vm->_dialogs->CloseDownConv();
837 	} else if ((fn == CONV_TOP) || (fn == CONV_DEF) || (fn == CONV_BOTTOM)) {
838 		// TOP of screen, Default (i.e. TOP of screen), or BOTTOM of screen
839 
840 		// If waiting is enabled, wait for ongoing scroll
841 		if (TinselV2 && SysVar(SV_CONVERSATIONWAITS))
842 			CORO_INVOKE_1(WaitScroll, myEscape);
843 
844 		// Don't do it if it's not wanted
845 		if (escOn && myEscape != GetEscEvents())
846 			return;
847 
848 		// Don't do it if already in a conversation
849 		if (_vm->_dialogs->IsConvWindow())
850 			return;
851 
852 		_vm->_dialogs->KillInventory();
853 
854 		if (TinselV2) {
855 			// If this is from a tag polygon, get the associated
856 			// actor (the one the polygon is named after), if any.
857 			if (!actor) {
858 				actor = GetTagPolyId(hp);
859 				if (actor & ACTORTAG_KEY)
860 					actor &= ~ACTORTAG_KEY;
861 				else
862 					actor = 0;
863 			}
864 
865 			// Top or bottom; tag polygon or tagged actor
866 			_vm->_dialogs->SetConvDetails((CONV_PARAM)fn, hp, actor);
867 		} else {
868 			_vm->_dialogs->convPos(fn);
869 			_vm->_dialogs->ConvPoly(hp);
870 		}
871 
872 		_vm->_dialogs->PopUpInventory(INV_CONV); // Conversation window
873 		_vm->_dialogs->ConvAction(INV_OPENICON); // CONVERSATION event
874 	}
875 
876 	CORO_END_CODE;
877 }
878 
879 /**
880  * Add icon to conversation window's permanent default list.
881  */
ConvTopic(int icon)882 static void ConvTopic(int icon) {
883 	_vm->_dialogs->PermaConvIcon(icon);
884 }
885 
886 /**
887  * ToggleCursor(on/off)
888  */
ToggleCursor(int onoff)889 void ToggleCursor(int onoff) {
890 	if (onoff) {
891 		// Re-instate cursor
892 		_vm->_cursor->UnHideCursor();
893 	} else {
894 		// Blank out cursor
895 		_vm->_cursor->DwHideCursor();
896 	}
897 }
898 
899 /**
900  * Returns the x or y position of the cursor.
901  */
CursorPos(int xory)902 static int CursorPos(int xory) {
903 	int x, y;
904 
905 	_vm->_cursor->GetCursorXY(&x, &y, true);
906 	return (xory == CURSORXPOS) ? x : y;
907 }
908 
909 /**
910  * Declare 3d model for an actor.
911  */
Dec3D(int ano,SCNHANDLE hModelName,SCNHANDLE hTextureName)912 void Dec3D(int ano, SCNHANDLE hModelName, SCNHANDLE hTextureName) {
913 	MOVER* pMover = GetMover(ano);
914 	assert(pMover != nullptr);
915 
916 	pMover->type = MOVER_3D;
917 	pMover->hModelName = hModelName;
918 	pMover->hTextureName = hTextureName;
919 
920 	// if (_hModelNameLoaded == 0) {
921 	// 	_hModelNameLoaded = hModelName;
922 	// 	const char* modelName = (const char *)_vm->_handle->LockMem(hModelName);
923 	// 	const char* textureName = (const char *)_vm->_handle->LockMem(hTextureName);
924 	// 	LoadModels(modelName, textureName);
925 	// }
926 	//assert(_hModelNameLoaded == hModelName);
927 }
928 
929 /**
930  * Declare conversation window.
931  */
DecConvW(SCNHANDLE text,int MaxContents,int MinWidth,int MinHeight,int StartWidth,int StartHeight,int MaxWidth,int MaxHeight)932 static void DecConvW(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight,
933 			int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) {
934 	_vm->_dialogs->idec_convw(text, MaxContents, MinWidth, MinHeight,
935 			StartWidth, StartHeight, MaxWidth, MaxHeight);
936 }
937 
938 /**
939  * Declare config strings.
940  */
DecCStrings(SCNHANDLE * tp)941 static void DecCStrings(SCNHANDLE *tp) {
942 	_vm->_dialogs->setConfigStrings(tp);
943 }
944 
945 /**
946  * Declare cursor's reels.
947  */
DecCursor(SCNHANDLE hFilm)948 static void DecCursor(SCNHANDLE hFilm) {
949 	_vm->_cursor->DwInitCursor(hFilm);
950 }
951 
952 /**
953  * Declare the language flags.
954  */
DecFlags(SCNHANDLE hFilm)955 static void DecFlags(SCNHANDLE hFilm) {
956 	_vm->_dialogs->setFlagFilms(hFilm);
957 }
958 
959 /**
960  * Declare inventory 1's parameters.
961  */
DecInv1(SCNHANDLE text,int MaxContents,int MinWidth,int MinHeight,int StartWidth,int StartHeight,int MaxWidth,int MaxHeight)962 static void DecInv1(SCNHANDLE text, int MaxContents,
963 		int MinWidth, int MinHeight,
964 		int StartWidth, int StartHeight,
965 		int MaxWidth, int MaxHeight) {
966 	_vm->_dialogs->idec_inv1(text, MaxContents, MinWidth, MinHeight,
967 			StartWidth, StartHeight, MaxWidth, MaxHeight);
968 }
969 
970 /**
971  * Declare inventory 2's parameters.
972  */
DecInv2(SCNHANDLE text,int MaxContents,int MinWidth,int MinHeight,int StartWidth,int StartHeight,int MaxWidth,int MaxHeight)973 static void DecInv2(SCNHANDLE text, int MaxContents,
974 		int MinWidth, int MinHeight,
975 		int StartWidth, int StartHeight,
976 		int MaxWidth, int MaxHeight) {
977 	_vm->_dialogs->idec_inv2(text, MaxContents, MinWidth, MinHeight,
978 			StartWidth, StartHeight, MaxWidth, MaxHeight);
979 }
980 
981 /**
982  * Declare the bits that the inventory windows are constructed from.
983  */
DecInvW(SCNHANDLE hf)984 static void DecInvW(SCNHANDLE hf) {
985 	_vm->_dialogs->setInvWinParts(hf);
986 }
987 
988 /**
989  * DeclareLanguage
990  */
DeclareLanguage(int languageId,SCNHANDLE hDescription,SCNHANDLE hFlagFilm)991 static void DeclareLanguage(int languageId, SCNHANDLE hDescription, SCNHANDLE hFlagFilm) {
992 	LanguageFacts(languageId, hDescription, hFlagFilm);
993 }
994 
995 /**
996  * Declare lead actor.
997  * @param id		Actor Id
998  * @param rp		Walk and stand reels for all the regular scales (v1 only)
999  * @param text		Tag text (v1 only)
1000  */
DecLead(uint32 id,SCNHANDLE * rp=0,SCNHANDLE text=0)1001 static void DecLead(uint32 id, SCNHANDLE *rp = 0, SCNHANDLE text = 0) {
1002 	PMOVER	pMover;		// Moving actor structure
1003 
1004 	if (TinselV2) {
1005 		// Tinsel 2 only specifies the lead actor Id
1006 		_vm->_actor->SetLeadId(id);
1007 		RegisterMover(id);
1008 
1009 	} else {
1010 
1011 		_vm->_actor->Tag_Actor(id, text, TAG_DEF); // The lead actor is automatically tagged
1012 		_vm->_actor->SetLeadId(id);                // Establish this as the lead
1013 		RegisterMover(id);			// Establish as a moving actor
1014 
1015 		pMover = GetMover(id);		// Get moving actor structure
1016 		assert(pMover);
1017 
1018 		// Store all those reels
1019 		int i, j;
1020 		for (i = 0; i < 5; ++i) {
1021 			for (j = 0; j < 4; ++j)
1022 				pMover->walkReels[i][j] = *rp++;
1023 			for (j = 0; j < 4; ++j)
1024 				pMover->standReels[i][j] = *rp++;
1025 			for (j = 0; j < 4; ++j)
1026 				pMover->talkReels[i][j] = *rp++;
1027 		}
1028 
1029 
1030 		for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) {
1031 			for (j = 0; j < 4; ++j) {
1032 				pMover->walkReels[i][j] = pMover->walkReels[4][j];
1033 				pMover->standReels[i][j] = pMover->standReels[2][j];
1034 				pMover->talkReels[i][j] = pMover->talkReels[4][j];
1035 			}
1036 		}
1037 	}
1038 }
1039 
1040 /**
1041  * DecScale("actor", scale, 12*"reel")
1042  * Define an actor's walk and stand reels for a scale.
1043  */
DecScale(int actor,int scale,SCNHANDLE wkl,SCNHANDLE wkr,SCNHANDLE wkf,SCNHANDLE wka,SCNHANDLE stl,SCNHANDLE str,SCNHANDLE stf,SCNHANDLE sta,SCNHANDLE tal,SCNHANDLE tar,SCNHANDLE taf,SCNHANDLE taa)1044 static void DecScale(int actor, int scale,
1045 		SCNHANDLE wkl, SCNHANDLE wkr, SCNHANDLE wkf, SCNHANDLE wka,
1046 		SCNHANDLE stl, SCNHANDLE str, SCNHANDLE stf, SCNHANDLE sta,
1047 		SCNHANDLE tal, SCNHANDLE tar, SCNHANDLE taf, SCNHANDLE taa) {
1048 	PMOVER pMover = GetMover(actor);
1049 	assert(pMover);
1050 
1051 	SetWalkReels(pMover, scale, wkl, wkr, wkf, wka);
1052 	SetStandReels(pMover, scale, stl, str, stf, sta);
1053 	SetTalkReels(pMover, scale, tal, tar, taf, taa);
1054 }
1055 
1056 /**
1057  * Remove an icon from the conversation window.
1058  */
DelIcon(int icon)1059 static void DelIcon(int icon) {
1060 	_vm->_dialogs->RemFromInventory(INV_CONV, icon);
1061 }
1062 
1063 /**
1064  * Delete the object from inventory 1 or 2.
1065  */
DelInv(int object)1066 static void DelInv(int object) {
1067 	if (!_vm->_dialogs->RemFromInventory(INV_1, object)) // Remove from inventory 1...
1068 		_vm->_dialogs->RemFromInventory(INV_2, object);  // ...or 2 (whichever)
1069 
1070 	_vm->_dialogs->DropItem(object); // Stop holding it
1071 }
1072 
1073 /**
1074  * DelTopic
1075  */
DelTopic(int icon)1076 static void DelTopic(int icon) {
1077 	_vm->_dialogs->RemFromInventory(INV_CONV, icon);
1078 }
1079 
1080 /**
1081  * Delete the object from inventory 1 or 2.
1082  */
Drop(int object)1083 static void Drop(int object) {
1084 	if (object == -1)
1085 		object = HeldObject();
1086 
1087 	if (!_vm->_dialogs->RemFromInventory(INV_1, object)) // Remove from inventory 1...
1088 		_vm->_dialogs->RemFromInventory(INV_2, object);  // ...or 2 (whichever)
1089 
1090 	_vm->_dialogs->DropItem(object); // Stop holding it
1091 }
1092 
1093 /**
1094  * Delete all objects from inventory 1 and 2.
1095  */
DropEverything()1096 static void DropEverything() {
1097 	_vm->_dialogs->HoldItem(NOOBJECT, false);
1098 
1099 	_vm->_dialogs->ClearInventory(INV_1);
1100 	_vm->_dialogs->ClearInventory(INV_2);
1101 }
1102 
1103 /**
1104  * EnableMenu
1105  */
EnableMenu()1106 static void EnableMenu() {
1107 	g_bEnableMenu = true;
1108 }
1109 
1110 /**
1111  * Kill an actor's current graphics.
1112  */
EndActor(int actor)1113 static void EndActor(int actor) {
1114 	_vm->_actor->dwEndActor(actor);
1115 }
1116 
1117 /**
1118  * Get the actor to look at the polygon.
1119  * If the actor is at the tag, do a StandTag().
1120  */
FaceTag(int actor,HPOLYGON hp)1121 static void FaceTag(int actor, HPOLYGON hp) {
1122 	PMOVER	pMover;		// Moving actor structure
1123 	int	nowx, nowy;
1124 	int	nodex, nodey;
1125 
1126 	assert(hp != NOPOLY);
1127 
1128 	/*
1129 	 * Get which moving actor it is
1130 	 */
1131 	pMover = GetMover(actor);
1132 	assert(pMover);
1133 	if (MoverHidden(pMover))
1134 		return;
1135 
1136 	/*
1137 	 * Stop the actor
1138 	 */
1139 	StopWalk(actor);
1140 
1141 	/*
1142 	 * Face the tag
1143 	 */
1144 	// See where node is and where actor is
1145 	GetPolyNode(hp, &nodex, &nodey);
1146 	_vm->_actor->GetActorPos(actor, &nowx, &nowy);
1147 
1148 	if (nowx == nodex && nowy == nodey) {
1149 		// Stood at the tag, don't face in silly direction
1150 		StandTag(actor, hp);
1151 	} else {
1152 		// Look towards polygon
1153 		GetPolyMidBottom(hp, &nodex, &nodey);
1154 		SetMoverDirection(pMover, GetDirection(nowx, nowy,
1155 						nodex, nodey,
1156 						GetMoverDirection(pMover),
1157 						NOPOLY, YB_X1_5));
1158 		SetMoverStanding(pMover);
1159 	}
1160 }
1161 
1162 /**
1163  * FadeMidi(in/out)
1164  */
FadeMidi(CORO_PARAM,int inout)1165 static void FadeMidi(CORO_PARAM, int inout) {
1166 	CORO_BEGIN_CONTEXT;
1167 	CORO_END_CONTEXT(_ctx);
1168 
1169 	CORO_BEGIN_CODE(_ctx);
1170 	assert(inout == FM_IN || inout == FM_OUT);
1171 
1172 	// To prevent compiler complaining
1173 	if (inout == FM_IN || inout == FM_OUT)
1174 		CORO_SLEEP(1);
1175 	CORO_END_CODE;
1176 }
1177 
1178 /**
1179  * Freeze the cursor, or not.
1180  */
FreezeCursor(bool bFreeze)1181 static void FreezeCursor(bool bFreeze) {
1182 	_vm->_cursor->DoFreezeCursor(bFreeze);
1183 }
1184 
1185 /**
1186  * Guess what.
1187  */
GetInvLimit(int invno)1188 static int GetInvLimit(int invno) {
1189 	return _vm->_dialogs->InvGetLimit(invno);
1190 }
1191 
1192 /**
1193  * Ghost
1194  */
Ghost(int actor,int tColor,int tPalOffset)1195 static void Ghost(int actor, int tColor, int tPalOffset) {
1196 	SetSysVar(ISV_GHOST_ACTOR, actor);
1197 	SetSysVar(ISV_GHOST_COLOR,  tColor);
1198 	SetSysVar(ISV_GHOST_BASE, tPalOffset);
1199 }
1200 
1201 /**
1202  *
1203  */
HailScene(SCNHANDLE scene)1204 static void HailScene(SCNHANDLE scene) {
1205 	DoHailScene(scene);
1206 }
1207 
1208 /**
1209  * Returns TRUE if the game has been restarted, FALSE if not.
1210  */
HasRestarted()1211 static bool HasRestarted() {
1212 	return g_bHasRestarted;
1213 }
1214 
1215 /**
1216  * See if an object is in the inventory.
1217  */
Have(int object)1218 int Have(int object) {
1219 	return (_vm->_dialogs->InventoryPos(object) != NOOBJECT);
1220 }
1221 
1222 /**
1223  * Returns which object is currently held.
1224  */
HeldObject()1225 static int HeldObject() {
1226 	return _vm->_dialogs->WhichItemHeld();
1227 }
1228 
1229 /**
1230  * Hides the specified actor
1231  */
HideActorFn(CORO_PARAM,int ano)1232 static void HideActorFn(CORO_PARAM, int ano) {
1233 	HideActor(coroParam, ano);
1234 }
1235 
1236 /**
1237  * Turn a blocking polygon off.
1238  */
HideBlock(int block)1239 static void HideBlock(int block) {
1240 	DisableBlock(block);
1241 }
1242 
1243 /**
1244  * Turn an effect polygon off.
1245  */
HideEffect(int effect)1246 static void HideEffect(int effect) {
1247 	DisableEffect(effect);
1248 }
1249 
1250 /**
1251  * Turn a path polygon off.
1252  */
HidePath(int path)1253 static void HidePath(int path) {
1254 	DisablePath(path);
1255 }
1256 
1257 /**
1258  * Turn a refer polygon off.
1259  */
HideRefer(int refer)1260 static void HideRefer(int refer) {
1261 	DisableRefer(refer);
1262 }
1263 
1264 /**
1265  * Turn a tag polygon off.
1266  */
HideTag(CORO_PARAM,int tag,HPOLYGON hp)1267 static void HideTag(CORO_PARAM, int tag, HPOLYGON hp) {
1268 	// Tag could be zero, meaning calling tag
1269 	DisableTag(coroParam, tag ? tag : GetTagPolyId(hp));
1270 }
1271 
1272 /**
1273  * Hold the specified object.
1274  */
Hold(int object)1275 static void Hold(int object) {
1276 	_vm->_dialogs->HoldItem(object, false);
1277 }
1278 
1279 /**
1280  * HookScene(scene, entrance, transition)
1281  */
HookScene(SCNHANDLE scene,int entrance,int transition)1282 void HookScene(SCNHANDLE scene, int entrance, int transition) {
1283 	SetHookScene(scene, entrance, transition);
1284 }
1285 
1286 /**
1287  * IdleTime
1288  */
IdleTime()1289 static int IdleTime() {
1290 	// If control is off, system is not idle
1291 	if (!ControlIsOn()) {
1292 		// Player doesn't currently have control
1293 		ResetIdleTime();
1294 
1295 		return 0;
1296 	} else {
1297 		// Player has control - return time since last event
1298 		int x = getUserEventTime() / ONE_SECOND;
1299 
1300 		return x;
1301 	}
1302 }
1303 
1304 /**
1305  * Set flag if InstantScroll(on), reset if InstantScroll(off)
1306  */
InstantScroll(int onoff)1307 void InstantScroll(int onoff) {
1308 	g_bInstantScroll = (onoff != 0);
1309 }
1310 
1311 /**
1312  * invdepict
1313  */
InvDepict(int object,SCNHANDLE hFilm)1314 static void InvDepict(int object, SCNHANDLE hFilm) {
1315 	_vm->_dialogs->SetObjectFilm(object, hFilm);
1316 }
1317 
1318 /**
1319  * See if an object is in the inventory.
1320  */
InInventory(int object)1321 int InInventory(int object) {
1322 	return (_vm->_dialogs->InventoryPos(object) != INV_NOICON);
1323 }
1324 
1325 /**
1326  * Open an inventory.
1327  */
Inventory(int invno,bool escOn,int myEscape)1328 static void Inventory(int invno, bool escOn, int myEscape) {
1329 	// Don't do it if it's not wanted
1330 	if (escOn && myEscape != GetEscEvents())
1331 		return;
1332 
1333 	assert((invno == INV_1 || invno == INV_2)); // Trying to open illegal inventory
1334 
1335 	_vm->_dialogs->PopUpInventory(invno);
1336 }
1337 
1338 /**
1339  * Alter inventory object's icon.
1340  */
InvPlay(int object,SCNHANDLE hFilm)1341 static void InvPlay(int object, SCNHANDLE hFilm) {
1342 	_vm->_dialogs->SetObjectFilm(object, hFilm);
1343 }
1344 
1345 /**
1346  * See if an object is in the inventory.
1347  */
InWhichInv(int object)1348 static int InWhichInv(int object) {
1349 	if (_vm->_dialogs->WhichItemHeld() == object)
1350 		return 0;
1351 
1352 	if (_vm->_dialogs->IsInInventory(object, INV_1))
1353 		return 1;
1354 
1355 	if (_vm->_dialogs->IsInInventory(object, INV_2))
1356 		return 2;
1357 
1358 	return -1;
1359 }
1360 
1361 /**
1362  * Kill an actor.
1363  */
KillActor(int actor)1364 static void KillActor(int actor) {
1365 	_vm->_actor->DisableActor(actor);
1366 }
1367 
1368 /**
1369  * Turn a blocking polygon off.
1370  */
KillBlock(int block)1371 static void KillBlock(int block) {
1372 	DisableBlock(block);
1373 }
1374 
1375 /**
1376  * Turn an exit off.
1377  */
KillExit(int exit)1378 static void KillExit(int exit) {
1379 	DisableExit(exit);
1380 }
1381 
1382 /**
1383  * Turn a tag off.
1384  */
KillTag(CORO_PARAM,int tagno)1385 static void KillTag(CORO_PARAM, int tagno) {
1386 	DisableTag(coroParam, tagno);
1387 }
1388 
1389 /**
1390  * Kills the specified global process
1391  */
KillGlobalProcess(uint32 procID)1392 static void KillGlobalProcess(uint32 procID) {
1393 	xKillGlobalProcess(procID);
1394 }
1395 
1396 /**
1397  * Kills the specified process
1398  */
KillProcess(uint32 procID)1399 static void KillProcess(uint32 procID) {
1400 	KillSceneProcess(procID);
1401 }
1402 
1403 /**
1404  * Returns the left or top offset of the screen.
1405  */
LToffset(int lort)1406 static int LToffset(int lort) {
1407 	int Loffset, Toffset;
1408 
1409 	_vm->_bg->PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
1410 	return (lort == SCREENXPOS) ? Loffset : Toffset;
1411 }
1412 
1413 /**
1414  * Set new cursor position.
1415  */
MoveCursor(int x,int y)1416 static void MoveCursor(int x, int y) {
1417 	_vm->_cursor->SetCursorXY(x, y);
1418 
1419 	g_controlX = x;		// Save these values so that
1420 	g_controlY = y;		// control(on) doesn't undo this
1421 }
1422 
1423 /**
1424  * MoveTag(tag, x, y)
1425  */
MoveTag(int tag,int x,int y,HPOLYGON hp)1426 void MoveTag(int tag, int x, int y, HPOLYGON hp) {
1427 	// Tag could be zero, meaning calling tag
1428 	MovePolygon(TAG, tag ? tag : GetTagPolyId(hp), x, y);
1429 }
1430 
1431 /**
1432  * MoveTagTo(tag, x, y)
1433  */
MoveTagTo(int tag,int x,int y,HPOLYGON hp)1434 void MoveTagTo(int tag, int x, int y, HPOLYGON hp) {
1435 	// Tag could be zero, meaning calling tag
1436 	MovePolygonTo(TAG, tag ? tag : GetTagPolyId(hp), x, y);
1437 }
1438 
1439 /**
1440  * Triggers change to a new scene.
1441  */
NewScene(CORO_PARAM,SCNHANDLE scene,int entrance,int transition)1442 void NewScene(CORO_PARAM, SCNHANDLE scene, int entrance, int transition) {
1443 	// COROUTINE
1444 	CORO_BEGIN_CONTEXT;
1445 	CORO_END_CONTEXT(_ctx);
1446 
1447 	CORO_BEGIN_CODE(_ctx);
1448 
1449 	if (TinselV2) {
1450 		if (_vm->_bmv->MoviePlaying()) {
1451 			_vm->_bmv->AbortMovie();
1452 			CORO_SLEEP(2);
1453 		}
1454 	}
1455 
1456 	SetNewScene(scene, entrance, transition);
1457 
1458 	// Prevent tags and cursor re-appearing
1459 	if (TinselV2)
1460 		ControlStartOff();
1461 	else
1462 		GetControl(CONTROL_STARTOFF);
1463 
1464 	if (TinselV1)
1465 		++g_sceneCtr;
1466 
1467 	// Prevent code subsequent to this call running before scene changes
1468 	if (CoroScheduler.getCurrentPID() != PID_MASTER_SCR)
1469 		CORO_KILL_SELF();
1470 	CORO_END_CODE;
1471 }
1472 
1473 /**
1474  * Disable dynamic blocking for current scene.
1475  */
NoBlocking()1476 static void NoBlocking() {
1477 	SetNoBlocking(true);
1478 }
1479 
1480 /**
1481  * Define a no-scroll boundary for the current scene.
1482  */
NoScroll(int x1,int y1,int x2,int y2)1483 static void NoScroll(int x1, int y1, int x2, int y2) {
1484 	_vm->_scroll->SetNoScroll(x1, y1, x2, y2);
1485 }
1486 
1487 /**
1488  * Hold the specified object.
1489  */
ObjectHeld(int object)1490 static void ObjectHeld(int object) {
1491 	_vm->_dialogs->HoldItem(object);
1492 }
1493 
1494 /**
1495  * Set the top left offset of the screen.
1496  */
Offset(EXTREME extreme,int x,int y)1497 void Offset(EXTREME extreme, int x, int y) {
1498 	_vm->_scroll->KillScroll();
1499 
1500 	if (TinselV2)
1501 		DecodeExtreme(extreme, &x, &y);
1502 
1503 	_vm->_bg->PlayfieldSetPos(FIELD_WORLD, x, y);
1504 }
1505 
1506 /**
1507  * OtherObject()
1508  */
OtherObject(INV_OBJECT * pinvo)1509 int OtherObject(INV_OBJECT *pinvo) {
1510 	assert(pinvo != NULL);
1511 
1512 	// return held object or object clicked on - whichever is not the calling object
1513 
1514 	// pinvo->id is the calling object
1515 	// WhichItemHeld() gives the held object
1516 	// GetIcon() gives the object clicked on
1517 
1518 	assert(_vm->_dialogs->GetIcon() == pinvo->id || _vm->_dialogs->WhichItemHeld() == pinvo->id);
1519 
1520 	if (_vm->_dialogs->GetIcon() == pinvo->id)
1521 		return _vm->_dialogs->WhichItemHeld();
1522 	else
1523 		return _vm->_dialogs->GetIcon();
1524 }
1525 
1526 /**
1527  * Play a film.
1528  */
Play(CORO_PARAM,SCNHANDLE hFilm,int x,int y,int compit,int actorid,bool splay,int sfact,bool escOn,int myEscape,bool bTop)1529 static void Play(CORO_PARAM, SCNHANDLE hFilm, int x, int y, int compit, int actorid, bool splay, int sfact,
1530 		  bool escOn, int myEscape, bool bTop) {
1531 	assert(hFilm != 0); // play(): Trying to play NULL film
1532 
1533 	// COROUTINE
1534 	CORO_BEGIN_CONTEXT;
1535 	CORO_END_CONTEXT(_ctx);
1536 
1537 	CORO_BEGIN_CODE(_ctx);
1538 
1539 
1540 	// Don't do CDPlay() for now if already escaped
1541 	if (g_bEscapedCdPlay) {
1542 		g_bEscapedCdPlay = false;
1543 		return;
1544 	}
1545 
1546 	// Don't do it if it's not wanted
1547 	if (escOn && myEscape != GetEscEvents())
1548 		return;
1549 
1550 	// If this actor is dead, call a stop to the calling process
1551 	if (actorid && !_vm->_actor->actorAlive(actorid))
1552 		CORO_KILL_SELF();
1553 
1554 	// 7/4/95
1555 	if (!escOn)
1556 		myEscape = GetEscEvents();
1557 
1558 	if (compit == 1) {
1559 		// Play to completion before returning
1560 		CORO_INVOKE_ARGS(PlayFilmc, (CORO_SUBCTX, hFilm, x, y, actorid, splay, sfact, escOn, myEscape, bTop, nullptr));
1561 	} else if (compit == 2) {
1562 		error("play(): compit == 2 - please advise John");
1563 	} else {
1564 		// Kick off the play and return.
1565 		CORO_INVOKE_ARGS(PlayFilm, (CORO_SUBCTX, hFilm, x, y, actorid, splay, sfact, escOn, myEscape, bTop, nullptr));
1566 	}
1567 	CORO_END_CODE;
1568 }
1569 
1570 /**
1571  * Play a film
1572  */
Play(CORO_PARAM,SCNHANDLE hFilm,int x,int y,int compit,int myEscape,bool bTop,TINSEL_EVENT event,HPOLYGON hPoly,int taggedActor)1573 static void Play(CORO_PARAM, SCNHANDLE hFilm, int x, int y, int compit, int myEscape, bool bTop, TINSEL_EVENT event, HPOLYGON hPoly, int taggedActor) {
1574 	OBJECT** playfield = nullptr;
1575 
1576 	CORO_BEGIN_CONTEXT;
1577 	CORO_END_CONTEXT(_ctx);
1578 
1579 	CORO_BEGIN_CODE(_ctx);
1580 
1581 	assert(hFilm != 0);
1582 
1583 	// Don't do CdPlay() for now if already escaped
1584 	if (g_bEscapedCdPlay) {
1585 		g_bEscapedCdPlay = false;
1586 		return;
1587 	}
1588 
1589 	if (TinselV3) {
1590 		CORO_INVOKE_0(_vm->_bg->WaitForBG);
1591 	}
1592 
1593 	if (event == TALKING) {
1594 		int	actor;
1595 		if (hPoly == NOPOLY) {
1596 			// Must be a tagged actor
1597 
1598 			assert(taggedActor && _vm->_actor->IsTaggedActor(taggedActor));
1599 			actor = taggedActor;
1600 		} else if (taggedActor == 0) {
1601 			// Must be a polygon with an actor ID
1602 			actor = GetTagPolyId(hPoly);
1603 			assert(actor & ACTORTAG_KEY);
1604 			actor &= ~ACTORTAG_KEY;
1605 		}
1606 		else {
1607 			return;
1608 		}
1609 
1610 		_vm->_actor->SetActorTalking(actor, true);
1611 		_vm->_actor->SetActorTalkFilm(actor, hFilm);
1612 	}
1613 
1614 	bool bComplete;
1615 
1616 	bComplete = compit;
1617 
1618 	if (TinselV3) {
1619 		bComplete = compit & 0x20;
1620 		if (bTop) {
1621 			playfield = _vm->_bg->GetPlayfieldList(FIELD_STATUS);
1622 		} else {
1623 			playfield = _vm->_bg->GetPlayfieldList(compit & 0x0F);
1624 		}
1625 	}
1626 
1627 	if (bComplete) {
1628 		// Play to completion before returning
1629 		CORO_INVOKE_ARGS(PlayFilmc, (CORO_SUBCTX, hFilm, x, y, 0, false, false, myEscape != 0, myEscape, bTop, playfield));
1630 	} else {
1631 		// Kick off the play and return.
1632 		CORO_INVOKE_ARGS(PlayFilm, (CORO_SUBCTX, hFilm, x, y, myEscape, bTop, playfield));
1633 	}
1634 
1635 	CORO_END_CODE;
1636 }
1637 
1638 
1639 /**
1640  * Play a midi file.
1641  */
PlayMidi(CORO_PARAM,SCNHANDLE hMidi,int loop,bool complete)1642 static void PlayMidi(CORO_PARAM, SCNHANDLE hMidi, int loop, bool complete) {
1643 	CORO_BEGIN_CONTEXT;
1644 	CORO_END_CONTEXT(_ctx);
1645 
1646 	CORO_BEGIN_CODE(_ctx);
1647 	assert(loop == MIDI_DEF || loop == MIDI_LOOP);
1648 
1649 	_vm->_music->PlayMidiSequence(hMidi, loop == MIDI_LOOP);
1650 
1651 	// This check&sleep was added in DW v2. It was most likely added to
1652 	// ensure that the MIDI song started playing before the next opcode
1653 	// is executed.
1654 	// In DW1, it messes up the script arguments when entering the secret
1655 	// door in the bookshelf in the library, leading to a crash, when the
1656 	// music volume is set to 0.
1657 	if (!_vm->_music->MidiPlaying() && TinselV2)
1658 		CORO_SLEEP(1);
1659 
1660 	if (complete) {
1661 		while (_vm->_music->MidiPlaying())
1662 			CORO_SLEEP(1);
1663 	}
1664 	CORO_END_CODE;
1665 }
1666 
1667 /**
1668  * Plays a movie
1669  */
1670 
PlayMovie(CORO_PARAM,SCNHANDLE hFileStem,int myEscape)1671 static void PlayMovie(CORO_PARAM, SCNHANDLE hFileStem, int myEscape) {
1672 	CORO_BEGIN_CONTEXT;
1673 		int i;
1674 	CORO_END_CONTEXT(_ctx);
1675 
1676 	CORO_BEGIN_CODE(_ctx);
1677 
1678 	if (myEscape && myEscape != GetEscEvents())
1679 		return;
1680 
1681 	// Get rid of the cursor
1682 	for (_ctx->i = 0; _ctx->i < 3; _ctx->i++) {
1683 		_vm->_cursor->DwHideCursor();
1684 		_vm->_cursor->DropCursor();
1685 		CORO_SLEEP(1);
1686 	}
1687 
1688 	// They claim to be getting "Can't play two movies at once!" error
1689 	while (_vm->_bmv->MoviePlaying())
1690 		CORO_SLEEP(1);
1691 
1692 	// Play the movie
1693 	CORO_INVOKE_2(_vm->_bmv->PlayBMV, hFileStem, myEscape);
1694 
1695 	CORO_END_CODE;
1696 }
1697 
1698 /**
1699  * Plays a movie
1700  */
t3PlayMovie(CORO_PARAM,SCNHANDLE hFileStem,int myEscape)1701 static void t3PlayMovie(CORO_PARAM, SCNHANDLE hFileStem, int myEscape) {
1702 	CORO_BEGIN_CONTEXT;
1703 		int i;
1704 		bool hadControl;
1705 	CORO_END_CONTEXT(_ctx);
1706 
1707 	CORO_BEGIN_CODE(_ctx);
1708 
1709 	if (myEscape && myEscape != GetEscEvents())
1710 		return;
1711 
1712 	_ctx->hadControl = GetControl();
1713 
1714 	while (_vm->_bmv->MoviePlaying()) {
1715 		CORO_SLEEP(1);
1716 	}
1717 
1718 	// Play the movie
1719 	CORO_INVOKE_2(_vm->_bmv->PlayBMV, hFileStem, myEscape);
1720 
1721 	if (_ctx->hadControl) {
1722 		ControlOn();
1723 	}
1724 
1725 	// Change scene
1726 
1727 	CORO_END_CODE;
1728 }
1729 
1730 
1731 /**
1732  * Play some music
1733  */
PlayMusic(int tune)1734 static void PlayMusic(int tune) {
1735 	_vm->_pcmMusic->startPlay(tune);
1736 }
1737 
FadeMusic(int tune,int fadeParams)1738 static void FadeMusic(int tune, int fadeParams) {
1739 	warning("TODO: Implement fading: %08x", fadeParams);
1740 	_vm->_pcmMusic->startPlay(tune);
1741 }
1742 
1743 /**
1744  * Play a sample.
1745  * Tinsel 1 version
1746  */
PlaySample(CORO_PARAM,int sample,bool bComplete,bool escOn,int myEscape)1747 static void PlaySample(CORO_PARAM, int sample, bool bComplete, bool escOn, int myEscape) {
1748 	CORO_BEGIN_CONTEXT;
1749 		Audio::SoundHandle handle;
1750 	CORO_END_CONTEXT(_ctx);
1751 
1752 	CORO_BEGIN_CODE(_ctx);
1753 	// Don't play SFX if voice is already playing
1754 	if (_vm->_mixer->hasActiveChannelOfType(Audio::Mixer::kSpeechSoundType))
1755 		return;
1756 
1757 	// Don't do it if it's not wanted
1758 	if (escOn && myEscape != GetEscEvents()) {
1759 		_vm->_sound->stopAllSamples();		// Stop any currently playing sample
1760 		return;
1761 	}
1762 
1763 	if (_vm->_config->_soundVolume != 0 && _vm->_sound->sampleExists(sample)) {
1764 		_vm->_sound->playSample(sample, Audio::Mixer::kSFXSoundType, &_ctx->handle);
1765 
1766 		if (bComplete) {
1767 			while (_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
1768 				// Abort if escapable and ESCAPE is pressed
1769 				if (escOn && myEscape != GetEscEvents()) {
1770 					_vm->_mixer->stopHandle(_ctx->handle);
1771 					break;
1772 				}
1773 
1774 				CORO_SLEEP(1);
1775 			}
1776 		}
1777 	} else {
1778 		// Prevent Glitter lock-up
1779 		CORO_SLEEP(1);
1780 	}
1781 	CORO_END_CODE;
1782 }
1783 
1784 /**
1785  * Play a sample
1786  * Tinsel 2 version
1787  */
PlaySample(CORO_PARAM,int sample,int x,int y,int flags,int myEscape)1788 static void PlaySample(CORO_PARAM, int sample, int x, int y, int flags, int myEscape) {
1789 	int	priority;
1790 	CORO_BEGIN_CONTEXT;
1791 		Audio::SoundHandle handle;
1792 		int myEscape;
1793 	CORO_END_CONTEXT(_ctx);
1794 
1795 	CORO_BEGIN_CODE(_ctx);
1796 
1797 	_ctx->myEscape = myEscape;
1798 
1799 	// Not escapable if PlaySample(..., s)
1800 	if (flags & PS_SUSTAIN) {
1801 		_ctx->myEscape = 0;
1802 		priority = PRIORITY_SPLAY2;
1803 	} else {
1804 		priority = PRIORITY_SPLAY1;
1805 	}
1806 
1807 	// Don't do anything if it's already been escaped
1808 	if (_ctx->myEscape && _ctx->myEscape != GetEscEvents())
1809 		return;
1810 
1811 	if (_vm->_config->_soundVolume != 0 && _vm->_sound->sampleExists(sample)) {
1812 		if (x == 0)
1813 			x = -1;
1814 
1815 		_vm->_sound->playSample(sample, 0, false, x, y, priority, Audio::Mixer::kSFXSoundType,
1816 			&_ctx->handle);
1817 
1818 		if (flags & PS_COMPLETE) {
1819 			while (_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
1820 				// Abort if escapable and ESCAPE is pressed
1821 				if (_ctx->myEscape && _ctx->myEscape != GetEscEvents()) {
1822 					_vm->_mixer->stopHandle(_ctx->handle);
1823 					break;
1824 				}
1825 
1826 				CORO_SLEEP(1);
1827 			}
1828 		}
1829 	} else {
1830 		// Prevent Glitter lock-up
1831 		CORO_SLEEP(1);
1832 	}
1833 
1834 	CORO_END_CODE;
1835 }
1836 
1837 /**
1838  * Move the cursor to the tagged actor's tag point.
1839  */
PointActor(int actor)1840 void PointActor(int actor) {
1841 	int	x, y;
1842 
1843 	// Only do this if the function is enabled
1844 	if (!SysVar(SV_ENABLEPOINTTAG))
1845 		return;
1846 
1847 	assert(_vm->_actor->IsTaggedActor(actor));
1848 
1849 	_vm->_actor->GetActorTagPos(actor, &x, &y, true);
1850 
1851 	_vm->setMousePosition(Common::Point(x, y));
1852 }
1853 
1854 /**
1855  * Move the cursor to the tag's tag point.
1856  */
PointTag(int tagno,HPOLYGON hp)1857 static void PointTag(int tagno, HPOLYGON hp) {
1858 	int	x, y;
1859 	SCNHANDLE junk;
1860 
1861 	// Only do this if the function is enabled
1862 	if (!SysVar(SV_ENABLEPOINTTAG))
1863 		return;
1864 
1865 	// Tag could be zero, meaning calling tag
1866 	if (tagno == 0)
1867 		tagno = GetTagPolyId(hp);
1868 
1869 	GetTagTag(GetTagHandle(tagno), &junk, &x, &y);
1870 
1871 	_vm->setMousePosition(Common::Point(x, y));
1872 }
1873 
1874 /**
1875  * PostActor("actor", event)
1876  */
PostActor(CORO_PARAM,int actor,TINSEL_EVENT event,HPOLYGON hp,int taggedActor,int myEscape)1877 static void PostActor(CORO_PARAM, int actor, TINSEL_EVENT event, HPOLYGON hp,
1878 			   int taggedActor, int myEscape) {
1879 	if (actor == -1) {
1880 		actor = taggedActor;
1881 		assert(hp == NOPOLY && taggedActor);
1882 		assert(_vm->_actor->IsTaggedActor(actor));
1883 	}
1884 
1885 	if (_vm->_actor->IsTaggedActor(actor)) {
1886 		assert(actor);
1887 		ActorEvent(coroParam, actor, event, false, myEscape);
1888 	} else {
1889 		PostTag(coroParam, actor | ACTORTAG_KEY, event, hp, myEscape);
1890 	}
1891 }
1892 
1893 /**
1894  * PostGlobalProcess(process#, event)
1895  */
PostGlobalProcess(CORO_PARAM,int procId,TINSEL_EVENT event,int myEscape)1896 static void PostGlobalProcess(CORO_PARAM, int procId, TINSEL_EVENT event, int myEscape) {
1897 	GlobalProcessEvent(coroParam, procId, event, false, myEscape);
1898 }
1899 
1900 /**
1901  * PostObject(object, event)
1902  */
PostObject(CORO_PARAM,int object,TINSEL_EVENT event,int myEscape)1903 static void PostObject(CORO_PARAM, int object, TINSEL_EVENT event, int myEscape) {
1904 	ObjectEvent(coroParam, object, event, false, myEscape);
1905 }
1906 
1907 /**
1908  * PostProcess(process#, event)
1909  */
PostProcess(CORO_PARAM,int procId,TINSEL_EVENT event,int myEscape)1910 static void PostProcess(CORO_PARAM, int procId, TINSEL_EVENT event, int myEscape) {
1911 	SceneProcessEvent(coroParam, procId, event, false, myEscape);
1912 }
1913 
1914 /**
1915  * Posts an event to a specified tag
1916  */
PostTag(CORO_PARAM,int tagno,TINSEL_EVENT event,HPOLYGON hp,int myEscape)1917 static void PostTag(CORO_PARAM, int tagno, TINSEL_EVENT event, HPOLYGON hp, int myEscape) {
1918 	// Tag could be zero, meaning calling tag
1919 	if (tagno == 0) {
1920 		assert(hp != NOPOLY);
1921 		PolygonEvent(coroParam, hp, event, 0, false, myEscape);
1922 	} else {
1923 		assert(IsTagPolygon(tagno));
1924 		PolygonEvent(coroParam, GetTagHandle(tagno), event, 0, false, myEscape);
1925 	}
1926 }
1927 
1928 /**
1929  * Trigger pre-loading of a scene's data.
1930  */
PrepareScene(SCNHANDLE scene)1931 static void PrepareScene(SCNHANDLE scene) {
1932 #ifdef BODGE
1933 	if (!_vm->_handle->ValidHandle(scene))
1934 		return;
1935 #endif
1936 }
1937 
1938 /**
1939  * Print the given text at the given place for the given time.
1940  */
Print(CORO_PARAM,int x,int y,SCNHANDLE text,int time,bool bSustain,bool escOn,int myEscape)1941 static void Print(CORO_PARAM, int x, int y, SCNHANDLE text, int time, bool bSustain, bool escOn, int myEscape) {
1942 	if (TinselV2)
1943 		escOn = myEscape != 0;
1944 
1945 	CORO_BEGIN_CONTEXT;
1946 		OBJECT *pText;			// text object pointer
1947 		int	myleftEvent;
1948 		bool bSample;			// Set if a sample is playing
1949 		Audio::SoundHandle handle;
1950 		int timeout;
1951 		int time;
1952 	CORO_END_CONTEXT(_ctx);
1953 
1954 	bool	bJapDoPrintText;	// Bodge to get-around Japanese bodge
1955 
1956 	CORO_BEGIN_CODE(_ctx);
1957 
1958 	_ctx->pText = nullptr;
1959 	_ctx->bSample = false;
1960 
1961 	// Don't do it if it's not wanted
1962 	if (escOn && myEscape != GetEscEvents())
1963 		return;
1964 
1965 	if (!TinselV2) {
1966 		// Kick off the voice sample
1967 		if (_vm->_config->_voiceVolume != 0 && _vm->_sound->sampleExists(text)) {
1968 			_vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
1969 			_ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle);
1970 		}
1971 	}
1972 
1973 	// Get the string
1974 	LoadStringRes(text, _vm->_font->TextBufferAddr(), TBUFSZ);
1975 
1976 	// Calculate display time
1977 	bJapDoPrintText = false;
1978 	if (time == 0) {
1979 		// This is a 'talky' print
1980 		_ctx->time = TextTime(_vm->_font->TextBufferAddr());
1981 
1982 		// Cut short-able if sustain was not set
1983 		_ctx->myleftEvent = bSustain ? 0 : GetLeftEvents();
1984 	} else {
1985 		_ctx->time = time * ONE_SECOND;
1986 		_ctx->myleftEvent = (TinselV2 && !bSustain) ? GetLeftEvents() : 0;
1987 		if (_vm->_config->isJapanMode())
1988 			bJapDoPrintText = true;
1989 	}
1990 
1991 	// Print the text
1992 	if (TinselV2) {
1993 		int Loffset, Toffset;
1994 		_vm->_bg->PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
1995 		_ctx->pText = ObjectTextOut(_vm->_bg->GetPlayfieldList(FIELD_STATUS),
1996 			_vm->_font->TextBufferAddr(), 0, x - Loffset, y - Toffset, _vm->_font->GetTagFontHandle(),
1997 			TXT_CENTER, 0);
1998 		assert(_ctx->pText);
1999 
2000 		// Adjust x, y, or z if necessary
2001 		KeepOnScreen(_ctx->pText, &x, &y);
2002 		if (_vm->_dialogs->IsTopWindow())
2003 			MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT);
2004 
2005 	} else if (bJapDoPrintText || (!_vm->_config->isJapanMode() && (_vm->_config->_useSubtitles || !_ctx->bSample))) {
2006 		int Loffset, Toffset;	// Screen position
2007 		_vm->_bg->PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
2008 		_ctx->pText = ObjectTextOut(_vm->_bg->GetPlayfieldList(FIELD_STATUS), _vm->_font->TextBufferAddr(),
2009 					0, x - Loffset, y - Toffset,
2010 					TinselV2 ? _vm->_font->GetTagFontHandle() : _vm->_font->GetTalkFontHandle(), TXT_CENTER);
2011 		assert(_ctx->pText); // string produced NULL text
2012 		if (_vm->_dialogs->IsTopWindow())
2013 			MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT);
2014 
2015 		/*
2016 		 * New feature: Don't go off the side of the background
2017 		 */
2018 		int	shift;
2019 		shift = MultiRightmost(_ctx->pText) + 2;
2020 		if (shift >= _vm->_bg->BgWidth())			// Not off right
2021 			MultiMoveRelXY(_ctx->pText, _vm->_bg->BgWidth() - shift, 0);
2022 		shift = MultiLeftmost(_ctx->pText) - 1;
2023 		if (shift <= 0)					// Not off left
2024 			MultiMoveRelXY(_ctx->pText, -shift, 0);
2025 		shift = MultiLowest(_ctx->pText);
2026 		if (shift > _vm->_bg->BgHeight())			// Not off bottom
2027 			MultiMoveRelXY(_ctx->pText, 0, _vm->_bg->BgHeight() - shift);
2028 	}
2029 
2030 	// Give up if nothing printed and no sample
2031 	if (_ctx->pText == NULL && !_ctx->bSample)
2032 		return;
2033 
2034 	// Leave it up until time runs out or whatever
2035 	if (TinselV2) {
2036 		do {
2037 			CORO_SLEEP(1);
2038 
2039 			// Cancelled?
2040 			if ( (myEscape && myEscape != GetEscEvents())
2041 					|| (!bSustain && LeftEventChange(_ctx->myleftEvent)))
2042 				break;
2043 
2044 		} while (_ctx->time-- >= 0);
2045 
2046 	} else {
2047 		_ctx->timeout = SAMPLETIMEOUT;
2048 		do {
2049 			CORO_SLEEP(1);
2050 
2051 			// Abort if escapable and ESCAPE is pressed
2052 			// Abort if left click - hardwired feature for talky-print!
2053 			// Will be ignored if myleftevent happens to be 0!
2054 			// Abort if sample times out
2055 			if ((escOn && myEscape != GetEscEvents())
2056 			|| (_ctx->myleftEvent && _ctx->myleftEvent != GetLeftEvents())
2057 			|| (_ctx->bSample && --_ctx->timeout <= 0))
2058 				break;
2059 
2060 			if (_ctx->bSample) {
2061 				// Wait for sample to end whether or not
2062 				if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
2063 					if (_ctx->pText == NULL || _vm->_config->_textSpeed == DEFTEXTSPEED)				{
2064 						// No text or speed modification - just depends on sample
2065 						break;
2066 					} else {
2067 						// Must wait for time
2068 						_ctx->bSample = false;
2069 					}
2070 				}
2071 			} else {
2072 				// No sample - just depends on time
2073 				if (_ctx->time-- <= 0)
2074 					break;
2075 			}
2076 
2077 		} while (1);
2078 	}
2079 
2080 	// Delete the text
2081 	if (_ctx->pText != NULL)
2082 		MultiDeleteObject(_vm->_bg->GetPlayfieldList(FIELD_STATUS), _ctx->pText);
2083 	_vm->_mixer->stopHandle(_ctx->handle);
2084 
2085 	CORO_END_CODE;
2086 }
2087 
2088 
2089 static void PrintObjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, OBJECT *&pText, const int textx, const int texty, const int item);
2090 static void PrintObjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText);
2091 
2092 /**
2093  * Print the given inventory object's name or whatever.
2094  */
PrintObj(CORO_PARAM,const SCNHANDLE hText,const INV_OBJECT * pinvo,const int event,int myEscape)2095 static void PrintObj(CORO_PARAM, const SCNHANDLE hText, const INV_OBJECT *pinvo, const int event, int myEscape) {
2096 	CORO_BEGIN_CONTEXT;
2097 		OBJECT *pText;		// text object pointer
2098 		int	textx, texty;
2099 		int	item;
2100 		bool bSample;
2101 		int sub;
2102 		Audio::SoundHandle handle;
2103 		int ticks;
2104 		int timeout;
2105 		bool bTookControl;
2106 		int myEscape;
2107 		int myLeftEvent;
2108 	CORO_END_CONTEXT(_ctx);
2109 
2110 	CORO_BEGIN_CODE(_ctx);
2111 
2112 	assert(pinvo != 0); // PrintObj() may only be called from an object code block
2113 	_ctx->myEscape = myEscape;
2114 
2115 	if (hText == (SCNHANDLE)-1) {	// 'OFF'
2116 		g_bNotPointedRunning = true;
2117 		return;
2118 	}
2119 	if (hText == (SCNHANDLE)-2) {	// 'ON'
2120 		g_bNotPointedRunning = false;
2121 		return;
2122 	}
2123 
2124 	// Don't do it if it's not wanted
2125 	if (TinselV2 && (myEscape) && (myEscape != GetEscEvents()))
2126 		return;
2127 
2128 	/*
2129 	* Find out which icon the cursor is over, and where to put the text.
2130 	*/
2131 	_vm->_cursor->GetCursorXY(&_ctx->textx, &_ctx->texty, false); // Cursor position..
2132 	_ctx->item = _vm->_dialogs->InvItem(&_ctx->textx, &_ctx->texty, true); // ..to text position
2133 	if (_ctx->item == INV_NOICON)
2134 		return;
2135 
2136 	/*
2137 	* POINT/other event PrintObj() arbitration...
2138 	*/
2139 	if (event != POINTED) {
2140 		g_bNotPointedRunning = true;	// Get POINTED text to die
2141 		CORO_SLEEP(1);		// Give it chance to
2142 	} else if (!TinselV2)
2143 		g_bNotPointedRunning = false;	// There may have been an OFF without an ON
2144 
2145 	// Make multi-ones escape
2146 	if (TinselV2 && (SubStringCount(hText) > 1) && !_ctx->myEscape)
2147 		_ctx->myEscape = GetEscEvents();
2148 
2149 	// Loop once for Tinsel 1 strings, and for Tinsel 2 however many lines are needed
2150 	for (_ctx->sub = 0; _ctx->sub < (TinselV2 ? SubStringCount(hText) : 1); _ctx->sub++) {
2151 		if (_ctx->myEscape && _ctx->myEscape != GetEscEvents())
2152 			break;
2153 
2154 		if (!_vm->_sound->sampleExists(hText))
2155 			_ctx->bSample = false;
2156 		else {
2157 			// Kick off the voice sample
2158 			_vm->_sound->playSample(hText, _ctx->sub, false, -1, -1, PRIORITY_TALK,
2159 				Audio::Mixer::kSpeechSoundType, &_ctx->handle);
2160 			_ctx->bSample = true;
2161 		}
2162 
2163 		// Display the text and set it's Z position
2164 		if (event == POINTED || (!_vm->_config->isJapanMode() && (_vm->_config->_useSubtitles || !_ctx->bSample))) {
2165 			int	xshift;
2166 
2167 			// Get the text string
2168 			if (TinselV2)
2169 				LoadSubString(hText, _ctx->sub, _vm->_font->TextBufferAddr(), TBUFSZ);
2170 			else
2171 				LoadStringRes(hText, _vm->_font->TextBufferAddr(), TBUFSZ);
2172 
2173 			_ctx->pText = ObjectTextOut(_vm->_bg->GetPlayfieldList(FIELD_STATUS), _vm->_font->TextBufferAddr(),
2174 						0, _ctx->textx, _ctx->texty, _vm->_font->GetTagFontHandle(), TXT_CENTER);
2175 			assert(_ctx->pText); // PrintObj() string produced NULL text
2176 
2177 			MultiSetZPosition(_ctx->pText, Z_INV_ITEXT);
2178 
2179 			if (TinselV2)
2180 				KeepOnScreen(_ctx->pText, &_ctx->textx, &_ctx->texty);
2181 			else {
2182 				// Don't go off the side of the screen
2183 				xshift = MultiLeftmost(_ctx->pText);
2184 				if (xshift < 0) {
2185 					MultiMoveRelXY(_ctx->pText, - xshift, 0);
2186 					_ctx->textx -= xshift;
2187 				}
2188 				xshift = MultiRightmost(_ctx->pText);
2189 				if (xshift > SCREEN_WIDTH) {
2190 					MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0);
2191 					_ctx->textx += SCREEN_WIDTH - xshift;
2192 				}
2193 			}
2194 		} else
2195 			_ctx->pText = nullptr;
2196 
2197 		if (TinselV2) {
2198 			if (event == POINTED) {
2199 				/*
2200 				* PrintObj() called from POINT event
2201 				*/
2202 				// Have to give way to non-POINTED-generated text
2203 				// and go away if the item gets picked up
2204 				int x, y;
2205 				do {
2206 					// Give up if this item gets picked up
2207 					if (_vm->_dialogs->WhichItemHeld() == pinvo->id)
2208 						break;
2209 
2210 					// Give way to non-POINTED-generated text
2211 					if (g_bNotPointedRunning) {
2212 						// Delete the text, and wait for the all-clear
2213 						MultiDeleteObject(_vm->_bg->GetPlayfieldList(FIELD_STATUS), _ctx->pText);
2214 						_ctx->pText = nullptr;
2215 
2216 						while (g_bNotPointedRunning)
2217 							CORO_SLEEP(1);
2218 
2219 						_vm->_cursor->GetCursorXY(&x, &y, false);
2220 						if (_vm->_dialogs->InvItem(&x, &y, false) != _ctx->item)
2221 							break;
2222 
2223 						// Re-display in the same place
2224 						LoadStringRes(hText, _vm->_font->TextBufferAddr(), TBUFSZ);
2225 						_ctx->pText = ObjectTextOut(_vm->_bg->GetPlayfieldList(FIELD_STATUS),
2226 							_vm->_font->TextBufferAddr(), 0, _ctx->textx, _ctx->texty, _vm->_font->GetTagFontHandle(),
2227 							TXT_CENTER, 0);
2228 						assert(_ctx->pText);
2229 
2230 						KeepOnScreen(_ctx->pText, &_ctx->textx, &_ctx->texty);
2231 						MultiSetZPosition(_ctx->pText, Z_INV_ITEXT);
2232 					}
2233 
2234 					CORO_SLEEP(1);
2235 
2236 					// Carry on until the cursor leaves this icon
2237 					_vm->_cursor->GetCursorXY(&x, &y, false);
2238 
2239 				} while (_vm->_dialogs->InvItemId(x, y) == pinvo->id);
2240 			} else {
2241 				/*
2242 				 * PrintObj() called from other event
2243 				 */
2244 				_ctx->myLeftEvent = GetLeftEvents();
2245 				_ctx->bTookControl = GetControl();
2246 
2247 				// Display for a time, but abort if conversation gets hidden
2248 				if (_ctx->pText)
2249 					_ctx->ticks = TextTime(_vm->_font->TextBufferAddr());
2250 				_ctx->timeout = SAMPLETIMEOUT;
2251 
2252 				for (;;) {
2253 					CORO_SLEEP(1);
2254 
2255 					// Abort if left click - hardwired feature for talky-print!
2256 					// Abort if sample times out
2257 					// Abort if conversation hidden
2258 					if (LeftEventChange(_ctx->myLeftEvent)
2259 							|| --_ctx->timeout <= 0 || _vm->_dialogs->ConvIsHidden())
2260 						break;
2261 
2262 					if (_ctx->bSample) {
2263 						// Wait for sample to end whether or not
2264 						if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
2265 							if (_ctx->pText == NULL || _vm->_config->_textSpeed == DEFTEXTSPEED) {
2266 								// No text or speed modification - just depends on sample
2267 								break;
2268 							} else {
2269 								// Must wait for time
2270 								_ctx->bSample = false;
2271 							}
2272 						}
2273 
2274 						// Decrement the subtitles timeout counter
2275 						if (_ctx->ticks > 0) --_ctx->ticks;
2276 
2277 					} else {
2278 						// No sample - just depends on time
2279 						if (_ctx->ticks-- <= 0)
2280 							break;
2281 					}
2282 				}
2283 
2284 				if (_ctx->bTookControl)
2285 					ControlOn();		// Free control if we took it
2286 			}
2287 
2288 		} else {
2289 			if (event == POINTED) {
2290 				// FIXME: Is there ever an associated sound if in POINTED mode???
2291 				assert(!_vm->_sound->sampleExists(hText));
2292 				CORO_INVOKE_ARGS(PrintObjPointed, (CORO_SUBCTX, hText, pinvo, _ctx->pText, _ctx->textx, _ctx->texty, _ctx->item));
2293 			} else {
2294 				CORO_INVOKE_2(PrintObjNonPointed, hText, _ctx->pText);
2295 			}
2296 		}
2297 
2298 		// Delete the text, if haven't already
2299 		if (_ctx->pText)
2300 			MultiDeleteObject(_vm->_bg->GetPlayfieldList(FIELD_STATUS), _ctx->pText);
2301 
2302 		// If it hasn't already finished, stop sample
2303 		if (_ctx->bSample)
2304 			_vm->_mixer->stopHandle(_ctx->handle);
2305 	}
2306 
2307 	// Let POINTED text back in if this is the last
2308 	if (event != POINTED)
2309 		g_bNotPointedRunning = false;
2310 
2311 	CORO_END_CODE;
2312 }
2313 
PrintObjPointed(CORO_PARAM,const SCNHANDLE text,const INV_OBJECT * pinvo,OBJECT * & pText,const int textx,const int texty,const int item)2314 static void PrintObjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, OBJECT *&pText, const int textx, const int texty, const int item) {
2315 	CORO_BEGIN_CONTEXT;
2316 	CORO_END_CONTEXT(_ctx);
2317 
2318 	CORO_BEGIN_CODE(_ctx);
2319 		// Have to give way to non-POINTED-generated text
2320 		// and go away if the item gets picked up
2321 		int	x, y;
2322 		do {
2323 			// Give up if this item gets picked up
2324 		    if (_vm->_dialogs->WhichItemHeld() == pinvo->id)
2325 				break;
2326 
2327 			// Give way to non-POINTED-generated text
2328 			if (g_bNotPointedRunning) {
2329 				// Delete the text, and wait for the all-clear
2330 				MultiDeleteObject(_vm->_bg->GetPlayfieldList(FIELD_STATUS), pText);
2331 				pText = nullptr;
2332 				while (g_bNotPointedRunning)
2333 					CORO_SLEEP(1);
2334 
2335 				_vm->_cursor->GetCursorXY(&x, &y, false);
2336 			    if (_vm->_dialogs->InvItem(&x, &y, false) != item)
2337 					break;
2338 
2339 				// Re-display in the same place
2340 				LoadStringRes(text, _vm->_font->TextBufferAddr(), TBUFSZ);
2341 				pText = ObjectTextOut(_vm->_bg->GetPlayfieldList(FIELD_STATUS), _vm->_font->TextBufferAddr(),
2342 							0, textx, texty, _vm->_font->GetTagFontHandle(), TXT_CENTER);
2343 				assert(pText); // PrintObj() string produced NULL text
2344 				MultiSetZPosition(pText, Z_INV_ITEXT);
2345 			}
2346 
2347 			CORO_SLEEP(1);
2348 
2349 			// Carry on until the cursor leaves this icon
2350 		    _vm->_cursor->GetCursorXY(&x, &y, false);
2351 	    } while (_vm->_dialogs->InvItemId(x, y) == pinvo->id);
2352 
2353 	CORO_END_CODE;
2354 }
2355 
PrintObjNonPointed(CORO_PARAM,const SCNHANDLE text,const OBJECT * pText)2356 static void PrintObjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText) {
2357 	CORO_BEGIN_CONTEXT;
2358 		bool bSample;		// Set if a sample is playing
2359 		Audio::SoundHandle handle;
2360 
2361 		int myleftEvent;
2362 		bool took_control;
2363 		int	ticks;
2364 		int	timeout;
2365 	CORO_END_CONTEXT(_ctx);
2366 
2367 	CORO_BEGIN_CODE(_ctx);
2368 		// Kick off the voice sample
2369 		if (_vm->_config->_voiceVolume != 0 && _vm->_sound->sampleExists(text)) {
2370 			_vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
2371 			_ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle);
2372 		} else
2373 			_ctx->bSample = false;
2374 
2375 		_ctx->myleftEvent = GetLeftEvents();
2376 		_ctx->took_control = GetControl(CONTROL_OFF);
2377 
2378 		// Display for a time, but abort if conversation gets hidden
2379 		if (_vm->_config->isJapanMode())
2380 			_ctx->ticks = JAP_TEXT_TIME;
2381 		else if (pText)
2382 			_ctx->ticks = TextTime(_vm->_font->TextBufferAddr());
2383 		else
2384 			_ctx->ticks = 0;
2385 
2386 		_ctx->timeout = SAMPLETIMEOUT;
2387 		do {
2388 			CORO_SLEEP(1);
2389 			--_ctx->timeout;
2390 
2391 			// Abort if left click - hardwired feature for talky-print!
2392 			// Abort if sample times out
2393 			// Abort if conversation hidden
2394 		    if (_ctx->myleftEvent != GetLeftEvents() || _ctx->timeout <= 0 || _vm->_dialogs->ConvIsHidden())
2395 				break;
2396 
2397 			if (_ctx->bSample) {
2398 				// Wait for sample to end whether or not
2399 				if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
2400 					if (pText == NULL || _vm->_config->_textSpeed == DEFTEXTSPEED) {
2401 						// No text or speed modification - just depends on sample
2402 						break;
2403 					} else {
2404 						// Must wait for time
2405 						_ctx->bSample = false;
2406 					}
2407 				}
2408 
2409 				// Decrement the subtitles timeout counter
2410 				if (_ctx->ticks > 0) --_ctx->ticks;
2411 
2412 			} else {
2413 				// No sample - just depends on time
2414 				if (_ctx->ticks-- <= 0)
2415 					break;
2416 			}
2417 		} while (1);
2418 
2419 		g_bNotPointedRunning = false;	// Let POINTED text back in
2420 
2421 		if (_ctx->took_control)
2422 			Control(CONTROL_ON);	// Free control if we took it
2423 
2424 		_vm->_mixer->stopHandle(_ctx->handle);
2425 
2426 	CORO_END_CODE;
2427 }
2428 
2429 /**
2430  * Register the fact that this poly would like its tag displayed.
2431  */
PrintTag(HPOLYGON hp,SCNHANDLE text,int actor=0,bool bCursor=false)2432 static void PrintTag(HPOLYGON hp, SCNHANDLE text, int actor = 0, bool bCursor = false) {
2433 	// printtag() may only be called from a polygon code block in Tinsel 1, or
2434 	// additionally from a moving actor code block in Tinsel 2
2435 	assert((hp != NOPOLY) || (TinselV2 && (actor != 0)));
2436 
2437 	if (hp != NOPOLY) {
2438 		// Poly handling
2439 		if (TinselV2)
2440 			SetPolyTagWanted(hp, true, bCursor, text);
2441 		else if (PolyTagState(hp) == TAG_OFF) {
2442 			SetPolyTagState(hp, TAG_ON);
2443 			SetPolyTagHandle(hp, text);
2444 		}
2445 	} else {
2446 		// Moving actor handling
2447 		_vm->_actor->SetActorTagWanted(actor, true, bCursor, text);
2448 	}
2449 }
2450 
2451 /**
2452  * Quits the game
2453  */
QuitGame()2454 static void QuitGame() {
2455 	_vm->_music->StopMidi();
2456 	StopSample();
2457 	_vm->quitGame();
2458 }
2459 
2460 /**
2461  * Return a random number between optional limits.
2462  */
RandomFn(int n1,int n2,int norpt)2463 static int RandomFn(int n1, int n2, int norpt) {
2464 	int i = 0;
2465 	uint32 value;
2466 
2467 	// In DW1 demo, upper/lower limit can be reversed
2468 	if (n2 < n1) SWAP(n1, n2);
2469 
2470 	do {
2471 		value = n1 + _vm->getRandomNumber(n2 - n1);
2472 	} while ((g_lastValue == value) && (norpt == RAND_NORPT) && (++i <= 10));
2473 
2474 	g_lastValue = value;
2475 	return value;
2476 }
2477 
2478 /**
2479  * ResetIdleTime
2480  */
ResetIdleTime()2481 void ResetIdleTime() {
2482 	resetUserEventTime();
2483 }
2484 
2485 /**
2486  * FnRestartGame
2487  */
FnRestartGame()2488 void FnRestartGame() {
2489 	// TODO: Tinsel 2 comments out the 2 calls, but I'm not sure that this should be done
2490 	_vm->_music->StopMidi();
2491 	StopSample();
2492 
2493 	g_bRestart = true;
2494 	g_sceneCtr = 0;
2495 }
2496 
2497 /**
2498  * Restore saved scene.
2499  */
RestoreScene(CORO_PARAM,TRANSITS transition)2500 static void RestoreScene(CORO_PARAM, TRANSITS transition) {
2501 	// COROUTINE
2502 	CORO_BEGIN_CONTEXT;
2503 	CORO_END_CONTEXT(_ctx);
2504 
2505 	CORO_BEGIN_CODE(_ctx);
2506 
2507 	if (TinselV2) {
2508 		if (_vm->_bmv->MoviePlaying()) {
2509 			_vm->_bmv->AbortMovie();
2510 			CORO_SLEEP(2);
2511 		}
2512 
2513 		CuttingScene(false);
2514 
2515 	} else {
2516 		UnSuspendHook();
2517 	}
2518 
2519 	TinselRestoreScene(transition == TRANS_FADE);
2520 
2521 	CORO_END_CODE;
2522 }
2523 
2524 /**
2525  * Resumes the last game
2526  */
ResumeLastGame()2527 void ResumeLastGame() {
2528 	RestoreGame(NewestSavedGame());
2529 }
2530 
2531 /**
2532  * Returns the current run mode
2533  */
RunMode()2534 static int RunMode() {
2535 	return 0;	//clRunMode;
2536 }
2537 
2538 /**
2539  * SamplePlaying
2540  */
SamplePlaying(bool escOn,int myEscape)2541 static bool SamplePlaying(bool escOn, int myEscape) {
2542 	// escape effects introduced 14/12/95 to fix
2543 	//	 while (sampleplaying()) pause;
2544 
2545 	if (escOn && myEscape != GetEscEvents())
2546 		return false;
2547 
2548 	return _vm->_sound->sampleIsPlaying();
2549 }
2550 
2551 /**
2552  * Save current scene.
2553  */
SaveScene(CORO_PARAM)2554 void SaveScene(CORO_PARAM) {
2555 	CORO_BEGIN_CONTEXT;
2556 	CORO_END_CONTEXT(_ctx);
2557 
2558 	CORO_BEGIN_CODE(_ctx);
2559 
2560 	if (TinselV2) {
2561 		CuttingScene(true);
2562 		SendSceneTinselProcess(LEAVE_T2);
2563 		CORO_GIVE_WAY;
2564 
2565 		CORO_INVOKE_0(TinselSaveScene);
2566 	} else {
2567 		CORO_INVOKE_0(TinselSaveScene);
2568 		SuspendHook();
2569 	}
2570 
2571 	CORO_END_CODE;
2572 }
2573 
2574 /**
2575  * ScalingReels
2576  */
ScalingReels(int actor,int scale,int direction,SCNHANDLE left,SCNHANDLE right,SCNHANDLE forward,SCNHANDLE away)2577 static void ScalingReels(int actor, int scale, int direction,
2578 		SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away) {
2579 
2580 	SetScalingReels(actor, scale, direction, left, right, forward, away);
2581 }
2582 
2583 /**
2584  * Return the icon that caused the CONVERSE event.
2585  */
ScanIcon()2586 static int ScanIcon() {
2587 	return _vm->_dialogs->GetIcon();
2588 }
2589 
2590 /**
2591  * Scroll the screen to target co-ordinates.
2592  */
ScrollScreen(CORO_PARAM,EXTREME extreme,int xp,int yp,int xIter,int yIter,bool bComp,bool escOn,int myEscape)2593 static void ScrollScreen(CORO_PARAM, EXTREME extreme, int xp, int yp, int xIter, int yIter, bool bComp, bool escOn, int myEscape) {
2594 	CORO_BEGIN_CONTEXT;
2595 		int	thisScroll;
2596 		int x, y;
2597 	CORO_END_CONTEXT(_ctx);
2598 
2599 	CORO_BEGIN_CODE(_ctx);
2600 
2601 	++g_scrollNumber;
2602 	_ctx->x = xp;
2603 	_ctx->y = yp;
2604 
2605 	if ((TinselV2 && g_bInstantScroll) || (escOn && myEscape != GetEscEvents())) {
2606 		// Instant completion!
2607 		Offset(extreme, _ctx->x, _ctx->y);
2608 	} else {
2609 		_ctx->thisScroll = g_scrollNumber;
2610 		if (TinselV2)
2611 			DecodeExtreme(extreme, &_ctx->x, &_ctx->y);
2612 
2613 		_vm->_scroll->ScrollTo(_ctx->x, _ctx->y, xIter, yIter);
2614 
2615 		if (bComp) {
2616 			int	Loffset, Toffset;
2617 			do {
2618 				CORO_SLEEP(1);
2619 
2620 				// If escapable and ESCAPE is pressed...
2621 				if (escOn && myEscape != GetEscEvents()) {
2622 					// Instant completion!
2623 					Offset(extreme, _ctx->x, _ctx->y);
2624 					break;
2625 				}
2626 
2627 				// give up if have been superseded
2628 				if (_ctx->thisScroll != g_scrollNumber)
2629 					CORO_KILL_SELF();
2630 
2631 				_vm->_bg->PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
2632 			} while (Loffset != _ctx->x || Toffset != _ctx->y);
2633 		} else if (TinselV2 && myEscape) {
2634 			SCROLL_MONITOR sm;
2635 
2636 			// Scroll is escapable even though we're not waiting for it
2637 			sm.x = _ctx->x;
2638 			sm.y = _ctx->y;
2639 			sm.thisScroll = g_scrollNumber;
2640 			sm.myEscape = myEscape;
2641 			CoroScheduler.createProcess(PID_TCODE, ScrollMonitorProcess, &sm, sizeof(sm));
2642 		}
2643 	}
2644 	CORO_END_CODE;
2645 }
2646 
2647 /**
2648  * ScrollParameters
2649  */
ScrollParameters(int xTrigger,int xDistance,int xSpeed,int yTriggerTop,int yTriggerBottom,int yDistance,int ySpeed)2650 static void ScrollParameters(int xTrigger, int xDistance, int xSpeed, int yTriggerTop,
2651 		int yTriggerBottom, int yDistance, int ySpeed) {
2652 	_vm->_scroll->SetScrollParameters(xTrigger, xDistance, xSpeed,
2653 			yTriggerTop, yTriggerBottom, yDistance, ySpeed);
2654 }
2655 
2656 /**
2657  * SendActor("actor", event)
2658  */
SendActor(CORO_PARAM,int actor,TINSEL_EVENT event,HPOLYGON hp,int myEscape)2659 int SendActor(CORO_PARAM, int actor, TINSEL_EVENT event, HPOLYGON hp, int myEscape) {
2660 	bool result;
2661 
2662 	if (_vm->_actor->IsTaggedActor(actor)) {
2663 		assert(actor);
2664 		ActorEvent(coroParam, actor, event, true, myEscape, &result);
2665 	} else {
2666 		SendTag(coroParam, actor | ACTORTAG_KEY, event, hp, myEscape, &result);
2667 	}
2668 
2669 	return result;
2670 }
2671 
2672 /**
2673  * SendGlobalProcess(process#, event)
2674  */
SendGlobalProcess(CORO_PARAM,int procId,TINSEL_EVENT event,int myEscape)2675 static int SendGlobalProcess(CORO_PARAM, int procId, TINSEL_EVENT event, int myEscape) {
2676 	return GlobalProcessEvent(coroParam, procId, event, true, myEscape);
2677 }
2678 
2679 /**
2680  * SendObject(object, event)
2681  */
SendObject(CORO_PARAM,int object,TINSEL_EVENT event,int myEscape)2682 static int SendObject(CORO_PARAM, int object, TINSEL_EVENT event, int myEscape) {
2683 	bool result;
2684 	ObjectEvent(coroParam, object, event, true, myEscape, &result);
2685 	return result;
2686 }
2687 
2688 /**
2689  * SendProcess(process#, event)
2690  */
SendProcess(CORO_PARAM,int procId,TINSEL_EVENT event,int myEscape)2691 static int SendProcess(CORO_PARAM, int procId, TINSEL_EVENT event, int myEscape) {
2692 	bool result;
2693 	SceneProcessEvent(coroParam, procId, event, true, myEscape, &result);
2694 	return result;
2695 }
2696 
2697 /**
2698  * SendTag(tag#, event)
2699  */
SendTag(CORO_PARAM,int tagno,TINSEL_EVENT event,HPOLYGON hp,int myEscape,bool * result)2700 static void SendTag(CORO_PARAM, int tagno, TINSEL_EVENT event, HPOLYGON hp, int myEscape, bool *result) {
2701 	// Tag could be zero, meaning calling tag
2702 	if (tagno == 0) {
2703 		assert(hp != NOPOLY);
2704 
2705 		PolygonEvent(coroParam, hp, event, 0, true, myEscape, result);
2706 	} else {
2707 		assert(IsTagPolygon(tagno));
2708 
2709 		PolygonEvent(coroParam, GetTagHandle(tagno), event, 0, true, myEscape, result);
2710 	}
2711 }
2712 
2713 /**
2714  * Un-kill an actor.
2715  */
SetActor(int actor)2716 static void SetActor(int actor) {
2717 	_vm->_actor->EnableActor(actor);
2718 }
2719 
2720 /**
2721  * Turn a blocking polygon on.
2722  */
2723 
SetBlock(int blockno)2724 static void SetBlock(int blockno) {
2725 	EnableBlock(blockno);
2726 }
2727 
2728 /**
2729  * Turn an exit on.
2730  */
2731 
SetExit(int exitno)2732 static void SetExit(int exitno) {
2733 	EnableExit(exitno);
2734 }
2735 
2736 /**
2737  * Guess what.
2738  */
SetInvLimit(int invno,int n)2739 static void SetInvLimit(int invno, int n) {
2740 	_vm->_dialogs->InvSetLimit(invno, n);
2741 }
2742 
2743 /**
2744  * Guess what.
2745  */
SetInvSize(int invno,int MinWidth,int MinHeight,int StartWidth,int StartHeight,int MaxWidth,int MaxHeight)2746 static void SetInvSize(int invno, int MinWidth, int MinHeight,
2747 		int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) {
2748 	_vm->_dialogs->InvSetSize(invno, MinWidth, MinHeight, StartWidth, StartHeight, MaxWidth, MaxHeight);
2749 }
2750 
2751 /**
2752  * Guess what.
2753  */
SetLanguage(LANGUAGE lang)2754 static void SetLanguage(LANGUAGE lang) {
2755 	assert(lang == TXT_ENGLISH || lang == TXT_FRENCH
2756 	     || lang == TXT_GERMAN  || lang == TXT_ITALIAN
2757 	     || lang == TXT_SPANISH); // ensure language is valid
2758 
2759 	ChangeLanguage(lang);
2760 }
2761 
2762 /**
2763  * Set palette
2764  */
SetPalette(SCNHANDLE hPal,bool escOn,int myEscape)2765 static void SetPalette(SCNHANDLE hPal, bool escOn, int myEscape) {
2766 	// Don't do it if it's not wanted
2767 	if (escOn && myEscape != GetEscEvents())
2768 		return;
2769 
2770 	_vm->_bg->ChangePalette(hPal);
2771 }
2772 
2773 /**
2774  * Set system reel
2775  */
SetSystemReel(int index,SCNHANDLE reel)2776 static void SetSystemReel(int index, SCNHANDLE reel) {
2777 	switch (index) {
2778 		case 11:
2779 			DecCursor(reel);
2780 			break;
2781 		default:
2782 			warning("SetSystemReel(%d, %08X), STUBBED", index, reel);
2783 			break;
2784 	}
2785 }
2786 
2787 /**
2788  * SetSystemString
2789  */
2790 
SetSystemString(int stringId,SCNHANDLE hString)2791 static void SetSystemString(int stringId, SCNHANDLE hString) {
2792 	SetSysString(stringId, hString);
2793 }
2794 
2795 /**
2796  * Set a system variable
2797  */
SetSystemVar(int varId,int newValue)2798 static void SetSystemVar(int varId, int newValue) {
2799 	SetSysVar(varId, newValue);
2800 }
2801 
2802 /**
2803  * Turn a tag on.
2804  */
SetTag(CORO_PARAM,int tagno)2805 static void SetTag(CORO_PARAM, int tagno) {
2806 	EnableTag(coroParam, tagno);
2807 }
2808 
2809 /**
2810  * Initialize a timer.
2811  */
SetTimer(int timerno,int start,bool up,bool frame)2812 static void SetTimer(int timerno, int start, bool up, bool frame) {
2813 	StartTimer(timerno, start, up != 0, frame != 0);
2814 }
2815 
2816 /**
2817  * Shell("cmdline")
2818  */
Shell(SCNHANDLE commandLine)2819 static void Shell(SCNHANDLE commandLine) {
2820 	LoadStringRes(commandLine, _vm->_font->TextBufferAddr(), TBUFSZ);
2821 	error("Tried to execute shell command \"%s\"", _vm->_font->TextBufferAddr());
2822 }
2823 
2824 /**
2825  * Don't hide an actors graphics.
2826  */
ShowActorFn(CORO_PARAM,int actor)2827 static void ShowActorFn(CORO_PARAM, int actor) {
2828 	ShowActor(coroParam, actor);
2829 }
2830 
2831 /**
2832  * Turn a blocking polygon on.
2833  */
ShowBlock(int blockno)2834 void ShowBlock(int blockno) {
2835 	EnableBlock(blockno);
2836 }
2837 
2838 /**
2839  * Turn an effect polygon on.
2840  */
ShowEffect(int effect)2841 void ShowEffect(int effect) {
2842 	EnableEffect(effect);
2843 }
2844 
2845 #ifdef DEBUG
2846 /**
2847  * Enable display of diagnostic co-ordinates.
2848  */
showpos()2849 static void showpos() {
2850 	setshowpos();
2851 }
2852 
2853 /**
2854  * Enable display of diagnostic co-ordinates.
2855  */
showstring()2856 static void showstring() {
2857 	setshowstring();
2858 }
2859 #endif
2860 
2861 /**
2862  * Shows the main menu
2863  */
ShowMenu()2864 static void ShowMenu() {
2865 	_vm->_dialogs->OpenMenu(MAIN_MENU);
2866 }
2867 
2868 /**
2869  * Turn a path on.
2870  */
ShowPath(int path)2871 static void ShowPath(int path) {
2872 	EnablePath(path);
2873 }
2874 
2875 /**
2876  * Turn a refer on.
2877  */
ShowRefer(int refer)2878 void ShowRefer(int refer) {
2879 	EnableRefer(refer);
2880 }
2881 
2882 /**
2883  * Turn a tag on.
2884  */
ShowTag(CORO_PARAM,int tag,HPOLYGON hp)2885 static void ShowTag(CORO_PARAM, int tag, HPOLYGON hp) {
2886 	// Tag could be zero, meaning calling tag
2887 	EnableTag(coroParam, tag ? tag : GetTagPolyId(hp));
2888 }
2889 
2890 /**
2891  * Special play - slow down associated actor's movement while the play
2892  * is running. After the play, position the actor where the play left
2893  * it and continue walking, if the actor still is.
2894  */
SPlay(CORO_PARAM,int sf,SCNHANDLE film,int x,int y,bool complete,int actorid,bool escOn,int myEscape)2895 static void SPlay(CORO_PARAM, int sf, SCNHANDLE film, int x, int y, bool complete, int actorid, bool escOn, int myEscape) {
2896 	// Don't do it if it's not wanted
2897 	if (escOn && myEscape != GetEscEvents())
2898 		return;
2899 
2900 	Play(coroParam, film, x, y, complete, actorid, true, sf, escOn, myEscape, false);
2901 }
2902 
2903 /**
2904  * (Re)Position an actor.
2905  * If moving actor is not around yet in this scene, start it up.
2906  */
Stand(CORO_PARAM,int actor,int x,int y,SCNHANDLE hFilm)2907 void Stand(CORO_PARAM, int actor, int x, int y, SCNHANDLE hFilm) {
2908 	CORO_BEGIN_CONTEXT;
2909 		PMOVER pMover;		// Moving actor structure
2910 	CORO_END_CONTEXT(_ctx);
2911 
2912 	CORO_BEGIN_CODE(_ctx);
2913 
2914 	_ctx->pMover = GetMover(actor);
2915 	assert(!TinselV2 || (_ctx->pMover != NULL));
2916 
2917 	if (_ctx->pMover) {
2918 		if (TinselV2) {
2919 			// New special. If no paths, just ignore this
2920 			if (PathCount() == 0)
2921 				return;
2922 
2923 			// Another new special.
2924 			// If lead actor, and TalkVia, ignore
2925 			if ((actor == _vm->_actor->GetLeadId() || actor == LEAD_ACTOR) && SysVar(ISV_DIVERT_ACTOR))
2926 				return;
2927 		}
2928 
2929 		if (!MoverIs(_ctx->pMover)) {
2930 			// create a moving actor process
2931 			MoverProcessCreate(x, y, (actor == LEAD_ACTOR) ? _vm->_actor->GetLeadId() : actor, _ctx->pMover);
2932 
2933 			if (hFilm == TF_NONE) {
2934 				// Make sure there is an assigned actorObj
2935 				while (!_ctx->pMover->actorObj)
2936 					CORO_SLEEP(1);
2937 
2938 				SetMoverStanding(_ctx->pMover);
2939 			} else {
2940 				// Check hFilm against certain constants. Note that a switch statement isn't
2941 				// used here because it would interfere with our co-routine implementation
2942 				if (hFilm == TF_UP) {
2943 					if (TinselV2) CORO_GIVE_WAY;
2944 					SetMoverDirection(_ctx->pMover, AWAY);
2945 					SetMoverStanding(_ctx->pMover);
2946 				} else if (hFilm == TF_DOWN) {
2947 					if (TinselV2) CORO_GIVE_WAY;
2948 					SetMoverDirection(_ctx->pMover, FORWARD);
2949 					SetMoverStanding(_ctx->pMover);
2950 				} else if (hFilm == TF_LEFT) {
2951 					if (TinselV2) CORO_GIVE_WAY;
2952 					SetMoverDirection(_ctx->pMover, LEFTREEL);
2953 					SetMoverStanding(_ctx->pMover);
2954 				} else if (hFilm == TF_RIGHT) {
2955 					if (TinselV2) CORO_GIVE_WAY;
2956 					SetMoverDirection(_ctx->pMover, RIGHTREEL);
2957 					SetMoverStanding(_ctx->pMover);
2958 				} else if (hFilm != TF_NONE) {
2959 					if (TinselV2) CORO_GIVE_WAY;
2960 					AlterMover(_ctx->pMover, hFilm, AR_NORMAL);
2961 				}
2962 			}
2963 		} else {
2964 			switch (hFilm) {
2965 			case TF_NONE:
2966 				if (x != -1 && y != -1)
2967 					PositionMover(_ctx->pMover, x, y);
2968 				break;
2969 
2970 			case TF_UP:
2971 				SetMoverDirection(_ctx->pMover, AWAY);
2972 				if (x != -1 && y != -1)
2973 					PositionMover(_ctx->pMover, x, y);
2974 				SetMoverStanding(_ctx->pMover);
2975 				break;
2976 			case TF_DOWN:
2977 				SetMoverDirection(_ctx->pMover, FORWARD);
2978 				if (x != -1 && y != -1)
2979 					PositionMover(_ctx->pMover, x, y);
2980 				SetMoverStanding(_ctx->pMover);
2981 				break;
2982 			case TF_LEFT:
2983 				SetMoverDirection(_ctx->pMover, LEFTREEL);
2984 				if (x != -1 && y != -1)
2985 					PositionMover(_ctx->pMover, x, y);
2986 				SetMoverStanding(_ctx->pMover);
2987 				break;
2988 			case TF_RIGHT:
2989 				SetMoverDirection(_ctx->pMover, RIGHTREEL);
2990 				if (x != -1 && y != -1)
2991 					PositionMover(_ctx->pMover, x, y);
2992 				SetMoverStanding(_ctx->pMover);
2993 				break;
2994 
2995 			default:
2996 				if (x != -1 && y != -1)
2997 					PositionMover(_ctx->pMover, x, y);
2998 				AlterMover(_ctx->pMover, hFilm, AR_NORMAL);
2999 				break;
3000 			}
3001 		}
3002 	} else if (actor == NULL_ACTOR) {
3003 		//
3004 	} else {
3005 		assert(hFilm != 0); // Trying to play NULL film
3006 
3007 		// Kick off the play and return.
3008 		CORO_INVOKE_ARGS(PlayFilm, (CORO_SUBCTX, hFilm, x, y, actor, false, 0, false, 0, false, nullptr));
3009 	}
3010 
3011 	CORO_END_CODE;
3012 }
3013 
3014 /**
3015  * Position the actor at the polygon's tag node.
3016  */
StandTag(int actor,HPOLYGON hp)3017 static void StandTag(int actor, HPOLYGON hp) {
3018 	SCNHANDLE hFilm;
3019 	int	pnodex, pnodey;
3020 
3021 	assert(hp != NOPOLY); // StandTag() may only be called from a polygon code block
3022 
3023 	// Where to stand
3024 	GetPolyNode(hp, &pnodex, &pnodey);
3025 
3026 	// Lead actor uses tag node film
3027 	hFilm = GetPolyFilm(hp);
3028 
3029 	// other actors can use direction
3030 	if (TinselV2) {
3031 		if (actor != LEAD_ACTOR && actor != _vm->_actor->GetLeadId()
3032 				&& hFilm != TF_UP && hFilm != TF_DOWN
3033 				&& hFilm != TF_LEFT && hFilm != TF_RIGHT)
3034 			hFilm = 0;
3035 
3036 		Stand(Common::nullContext, actor, pnodex, pnodey, hFilm);
3037 
3038 	} else if (hFilm && (actor == LEAD_ACTOR || actor == _vm->_actor->GetLeadId()))
3039 		Stand(Common::nullContext, actor, pnodex, pnodey, hFilm);
3040 	else
3041 		Stand(Common::nullContext, actor, pnodex, pnodey, 0);
3042 }
3043 
3044 
3045 /**
3046  * StartGlobalProcess
3047  */
StartGlobalProcess(CORO_PARAM,uint32 procID)3048 static void StartGlobalProcess(CORO_PARAM, uint32 procID) {
3049 	GlobalProcessEvent(coroParam, procID, STARTUP, false, 0);
3050 }
3051 
3052 /**
3053  * StartProcess
3054  */
StartProcess(CORO_PARAM,uint32 procID)3055 static void StartProcess(CORO_PARAM, uint32 procID) {
3056 	SceneProcessEvent(coroParam, procID, STARTUP, false, 0);
3057 }
3058 
3059 /**
3060  * Initialize a timer.
3061  */
StartTimerFn(int timerno,int start,bool up,int fs)3062 static void StartTimerFn(int timerno, int start, bool up, int fs) {
3063 	StartTimer(timerno, start, up, fs);
3064 }
3065 
StopMidiFn()3066 void StopMidiFn() {
3067 	_vm->_music->StopMidi();		// Stop any currently playing midi
3068 }
3069 
3070 /**
3071  * Kill a specific sample, or all samples.
3072  */
StopSample(int sample)3073 void StopSample(int sample) {
3074 	if (sample == -1)
3075 		_vm->_sound->stopAllSamples();		// Stop any currently playing sample
3076 	else
3077 		_vm->_sound->stopSpecSample(sample, 0);
3078 }
3079 
3080 /**
3081  * Kill a moving actor's walk.
3082  */
StopWalk(int actor)3083 static void StopWalk(int actor) {
3084 	PMOVER pMover;
3085 
3086 	pMover = GetMover(actor);
3087 	assert(pMover);
3088 
3089 	if (TinselV2) {
3090 		if (MoverHidden(pMover))
3091 			return;
3092 
3093 		StopMover(pMover);		// Cause the actor to stop
3094 	} else {
3095 		GetToken(pMover->actorToken);	// Kill the walk process
3096 		pMover->bStop = true;			// Cause the actor to stop
3097 		FreeToken(pMover->actorToken);
3098 	}
3099 }
3100 
3101 /**
3102  * Subtitles on/off
3103  */
Subtitles(int onoff)3104 static void Subtitles(int onoff) {
3105 	assert (onoff == ST_ON || onoff == ST_OFF);
3106 
3107 	if (_vm->_config->isJapanMode())
3108 		return;	// Subtitles are always off in JAPAN version (?)
3109 
3110 	_vm->_config->_useSubtitles = (onoff == ST_ON);
3111 }
3112 
3113 /**
3114  * Special walk.
3115  * Walk into or out of a legal path.
3116  */
Swalk(CORO_PARAM,int actor,int x1,int y1,int x2,int y2,SCNHANDLE film,int32 zOverride,bool escOn,int myEscape)3117 static void Swalk(CORO_PARAM, int actor, int x1, int y1, int x2, int y2, SCNHANDLE film, int32 zOverride, bool escOn, int myEscape) {
3118 	CORO_BEGIN_CONTEXT;
3119 		bool	bTookControl;			// Set if this function takes control
3120 	CORO_END_CONTEXT(_ctx);
3121 
3122 	HPOLYGON hPath;
3123 
3124 	CORO_BEGIN_CODE(_ctx);
3125 
3126 	// Don't do it if it's not wanted
3127 	if (escOn && myEscape != GetEscEvents()) {
3128 		if (TinselV2) {
3129 			if (x2 == -1 && y2 == -1)
3130 				CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x1, y1, 0));
3131 			else
3132 				CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x2, y2, 0));
3133 		}
3134 
3135 		return;
3136 	}
3137 
3138 	// For lead actor, lock out the user (if not already locked out)
3139 	if (actor == _vm->_actor->GetLeadId() || actor == LEAD_ACTOR) {
3140 		_ctx->bTookControl = GetControl(CONTROL_OFFV2);
3141 		if (TinselV2 && _ctx->bTookControl)
3142 			_vm->_cursor->RestoreMainCursor();
3143 	} else {
3144 		_ctx->bTookControl = false;
3145 	}
3146 
3147 	if (TinselV2 && (x2 == -1) && (y2 == -1)) {
3148 		// First co-ordinates are the destination
3149 		x2 = x1;
3150 		y2 = y1;
3151 	} else {
3152 		// Stand at the start co-ordinates
3153 		hPath = InPolygon(x1, y1, PATH);
3154 
3155 		if (hPath != NOPOLY) {
3156 			// Walking out of a path
3157 			CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x1, y1, 0));
3158 		} else {
3159 			hPath = InPolygon(x2, y2, PATH);
3160 			// One of them has to be in a path
3161 			assert(hPath != NOPOLY); //one co-ordinate must be in a legal path
3162 
3163 			// Walking into a path
3164 			CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x2, y2, 0));	// Get path's characteristics
3165 			CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x1, y1, 0));
3166 		}
3167 
3168 		if (TinselV2 && (zOverride != -1)) {
3169 			PMOVER pMover = GetMover(actor);
3170 			assert(pMover);
3171 
3172 			SetMoverZ(pMover, y1, zOverride);
3173 		}
3174 	}
3175 
3176 	CORO_INVOKE_ARGS(Walk, (CORO_SUBCTX, actor, x2, y2, film, 0, true, zOverride, escOn, myEscape));
3177 
3178 	// Free control if we took it
3179 	if (_ctx->bTookControl)
3180 		Control(CONTROL_ON);
3181 
3182 	CORO_END_CODE;
3183 }
3184 
3185 /**
3186  * Gets a system variable
3187  */
SystemVar(int varId)3188 static int SystemVar(int varId) {
3189 	return SysVar(varId);
3190 }
3191 
3192 /**
3193  * Define a tagged actor.
3194  */
TagActor(int actor,SCNHANDLE text,int tp)3195 static void TagActor(int actor, SCNHANDLE text, int tp) {
3196 	_vm->_actor->Tag_Actor(actor, text, tp);
3197 }
3198 
3199 /**
3200  * TagPos([tag #])
3201  */
TagPos(MASTER_LIB_CODES operand,int tagno,HPOLYGON hp)3202 static int TagPos(MASTER_LIB_CODES operand, int tagno, HPOLYGON hp) {
3203 	int	x, y;
3204 
3205 	// Tag could be zero, meaning calling tag
3206 	if (tagno == 0)
3207 		tagno = GetTagPolyId(hp);
3208 
3209 	if (operand == TAGTAGXPOS || operand == TAGTAGYPOS) {
3210 		SCNHANDLE junk;
3211 
3212 		GetTagTag(GetTagHandle(tagno), &junk, &x, &y);
3213 	} else {
3214 		GetPolyNode(GetTagHandle(tagno), &x, &y);
3215 	}
3216 
3217 	if (operand == TAGTAGXPOS || operand == TAGWALKXPOS)
3218 		return x;
3219 	else
3220 		return y;
3221 }
3222 
3223 /**
3224  * Text goes over actor's head while actor plays the talk reel.
3225  */
FinishTalkingReel(CORO_PARAM,PMOVER pMover,int actor)3226 static void FinishTalkingReel(CORO_PARAM, PMOVER pMover, int actor) {
3227 	CORO_BEGIN_CONTEXT;
3228 	CORO_END_CONTEXT(_ctx);
3229 
3230 	CORO_BEGIN_CODE(_ctx);
3231 
3232 	if (pMover) {
3233 		SetMoverStanding(pMover);
3234 		AlterMover(pMover, 0, AR_POPREEL);
3235 	} else {
3236 		_vm->_actor->SetActorTalking(actor, false);
3237 		CORO_INVOKE_ARGS(PlayFilm, (CORO_SUBCTX, _vm->_actor->GetActorPlayFilm(actor), -1, -1, 0, false, 0, false, 0, false, _vm->_bg->GetPlayfieldList(FIELD_WORLD)));
3238 	}
3239 
3240 	CORO_END_CODE;
3241 }
3242 
TalkOrSay(CORO_PARAM,SPEECH_TYPE speechType,SCNHANDLE hText,int x,int y,SCNHANDLE hFilm,int actorId,bool bSustain,bool escOn,int myEscape)3243 static void TalkOrSay(CORO_PARAM, SPEECH_TYPE speechType, SCNHANDLE hText, int x, int y,
3244 					  SCNHANDLE hFilm, int actorId, bool bSustain, bool escOn, int myEscape) {
3245 	CORO_BEGIN_CONTEXT;
3246 		int		Loffset, Toffset;	// Top left of display
3247 		int		actor;			// The speaking actor
3248 		PMOVER	pActor;			// For moving actors
3249 		int		myLeftEvent;
3250 		int		escEvents;
3251 		int		ticks;
3252 		bool	bTookControl;	// Set if this function takes control
3253 		bool	bTookTags;		// Set if this function disables tags
3254 		OBJECT	*pText;			// text object pointer
3255 		bool	bSample;		// Set if a sample is playing
3256 		bool	bSamples;
3257 		bool	bTalkReel;		// Set while talk reel is playing
3258 		Audio::SoundHandle handle;
3259 		int	timeout;
3260 
3261 		SPEECH_TYPE whatSort;
3262 		TFTYPE	direction;
3263 		int sub;
3264 		int x, y;
3265 	CORO_END_CONTEXT(_ctx);
3266 
3267 	CORO_BEGIN_CODE(_ctx);
3268 
3269 	_ctx->whatSort = speechType;
3270 	_ctx->escEvents = myEscape;
3271 	_ctx->x = x;
3272 	_ctx->y = y;
3273 	_ctx->Loffset = 0;
3274 	_ctx->Toffset = 0;
3275 	_ctx->ticks = 0;
3276 	_ctx->pText = nullptr;
3277 
3278 	// If waiting is enabled, wait for ongoing scroll
3279 	if (TinselV2 && SysVar(SV_SPEECHWAITS))
3280 		CORO_INVOKE_1(WaitScroll, myEscape);
3281 
3282 	// Don't do it if it's not wanted
3283 	if (escOn && myEscape != GetEscEvents())
3284 		return;
3285 
3286 	_ctx->myLeftEvent = GetLeftEvents();
3287 
3288 	// If this actor is dead, call a stop to the calling process
3289 	if (!TinselV2 && (actorId && !_vm->_actor->actorAlive(actorId)))
3290 		CORO_KILL_SELF();
3291 
3292 	if (!TinselV2 || (speechType == IS_TALK)) {
3293 		/*
3294 		 * Find out which actor is talking
3295 		 * and with which direction if no film supplied
3296 		 */
3297 		switch (hFilm) {
3298 		case TF_NONE:
3299 		case TF_UP:
3300 		case TF_DOWN:
3301 		case TF_LEFT:
3302 		case TF_RIGHT:
3303 			_ctx->actor = _vm->_actor->GetLeadId(); // If no film, actor is lead actor
3304 			_ctx->direction = (TFTYPE)hFilm;
3305 			break;
3306 
3307 		default:
3308 			_ctx->actor = ExtractActor(hFilm);
3309 			assert(_ctx->actor); // talk() - no actor ID in the reel
3310 			_ctx->direction = TF_FILM;
3311 			break;
3312 		}
3313 		assert(_ctx->actor);
3314 	} else if (TinselV2)
3315 		_ctx->actor = actorId;
3316 
3317 	/*
3318 	 * Lock out the user (for lead actor, if not already locked out)
3319 	 * May need to disable tags for other actors
3320 	 */
3321 	if (_ctx->actor == _vm->_actor->GetLeadId() || (TinselV2 && (_ctx->actor == LEAD_ACTOR)))
3322 		_ctx->bTookControl = GetControl(CONTROL_OFF);
3323 	else
3324 		_ctx->bTookControl = false;
3325 	_ctx->bTookTags = DisableTagsIfEnabled();
3326 
3327 	if (TinselV2) {
3328 		/*
3329 		 * Divert stuff
3330 		 */
3331 		if (SysVar(ISV_DIVERT_ACTOR) && (_ctx->actor == _vm->_actor->GetLeadId() || _ctx->actor == LEAD_ACTOR)) {
3332 			_ctx->actor = SysVar(ISV_DIVERT_ACTOR);
3333 			if (_ctx->whatSort == IS_TALK)
3334 				_ctx->whatSort = IS_SAY;
3335 			else if (_ctx->whatSort == IS_TALKAT)
3336 				_ctx->whatSort = IS_SAYAT;
3337 		}
3338 	}
3339 
3340 	/*
3341 	 * Kick off the voice sample
3342 	 */
3343 	if (_vm->_config->_voiceVolume != 0 && _vm->_sound->sampleExists(hText)) {
3344 		if (!TinselV2) {
3345 			_vm->_sound->playSample(hText, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
3346 			_ctx->bSamples = _vm->_mixer->isSoundHandleActive(_ctx->handle);
3347 		} else
3348 			_ctx->bSamples = true;
3349 	} else
3350 		_ctx->bSamples = false;
3351 
3352 	/*
3353 	 * Replace actor with the talk reel, saving the current one
3354 	 */
3355 	_ctx->pActor = GetMover(_ctx->actor);
3356 	if (_ctx->whatSort == IS_TALK) {
3357 		if (_ctx->pActor) {
3358 			if (_ctx->direction != TF_FILM)
3359 				hFilm = GetMoverTalkReel(_ctx->pActor, _ctx->direction);
3360 			AlterMover(_ctx->pActor, hFilm, AR_PUSHREEL);
3361 		} else {
3362 			_vm->_actor->SetActorTalking(_ctx->actor, true);
3363 			_vm->_actor->SetActorTalkFilm(_ctx->actor, hFilm);
3364 			CORO_INVOKE_ARGS(PlayFilm, (CORO_SUBCTX, hFilm, -1, -1, 0, false, 0, escOn, myEscape, false, _vm->_bg->GetPlayfieldList(FIELD_WORLD)));
3365 		}
3366 		_ctx->bTalkReel = true;
3367 		CORO_SLEEP(1);		// Allow the play to come in
3368 
3369 	} else if (_ctx->whatSort == IS_TALKAT) {
3370 		_ctx->bTalkReel = false;
3371 
3372 	} else if ((_ctx->whatSort == IS_SAY) || (_ctx->whatSort == IS_SAYAT)) {
3373 		_ctx->bTalkReel = false;
3374 		if (_vm->_actor->IsTaggedActor(_ctx->actor)) {
3375 			CORO_INVOKE_ARGS(ActorEvent, (CORO_SUBCTX, _ctx->actor, TALKING, false, 0));
3376 		} else if (IsTagPolygon(_ctx->actor | ACTORTAG_KEY)) {
3377 			CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX, GetTagHandle(_ctx->actor | ACTORTAG_KEY),
3378 				TALKING, 0, false, 0));
3379 		}
3380 
3381 		if (TinselV2)
3382 			// Let it all kick in and position this 'waiting' process
3383 			// down the process list from the playing process(es)
3384 			// This ensures immediate return when the reel finishes
3385 			CORO_GIVE_WAY;
3386 	}
3387 
3388 	// Make multi-ones escape
3389 	if (TinselV2 && (SubStringCount(hText) > 1) && !_ctx->escEvents)
3390 		_ctx->escEvents = GetEscEvents();
3391 
3392 	for (_ctx->sub = 0; _ctx->sub < (TinselV2 ? SubStringCount(hText) : 1); _ctx->sub++) {
3393 		if (TinselV2 && _ctx->escEvents && _ctx->escEvents != GetEscEvents())
3394 			break;
3395 
3396 		/*
3397 		 * Display the text.
3398 		 */
3399 		_ctx->bSample = _ctx->bSamples;
3400 		_ctx->pText = nullptr;
3401 
3402 		if (_vm->_config->isJapanMode()) {
3403 			_ctx->ticks = JAP_TEXT_TIME;
3404 		} else if (_vm->_config->_useSubtitles || !_ctx->bSample) {
3405 			/*
3406 			 * Work out where to display the text
3407 			 */
3408 			int	xshift, yshift;
3409 
3410 			_vm->_bg->PlayfieldGetPos(FIELD_WORLD, &_ctx->Loffset, &_ctx->Toffset);
3411 			if ((_ctx->whatSort == IS_SAY) || (_ctx->whatSort == IS_TALK))
3412 				_vm->_actor->GetActorMidTop(_ctx->actor, &_ctx->x, &_ctx->y);
3413 
3414 			if (!TinselV0 && !TinselV3) {
3415 				SetTextPal(_vm->_actor->GetActorRGB(_ctx->actor));
3416 			}
3417 			if (TinselV2) {
3418 				LoadSubString(hText, _ctx->sub, _vm->_font->TextBufferAddr(), TBUFSZ);
3419 			} else {
3420 				LoadStringRes(hText, _vm->_font->TextBufferAddr(), TBUFSZ);
3421 
3422 				_ctx->y -= _ctx->Toffset;
3423 			}
3424 
3425 			int color = 0;
3426 			if (TinselV3) {
3427 				color = _vm->_actor->GetActorRGB(_ctx->actor);
3428 			}
3429 
3430 			_ctx->pText = ObjectTextOut(_vm->_bg->GetPlayfieldList(FIELD_STATUS),
3431 					_vm->_font->TextBufferAddr(), color, _ctx->x - _ctx->Loffset, _ctx->y - _ctx->Toffset,
3432 					_vm->_font->GetTalkFontHandle(), TXT_CENTER);
3433 			assert(_ctx->pText); // talk() string produced NULL text;
3434 
3435 			if (_vm->_dialogs->IsTopWindow())
3436 				MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT);
3437 
3438 			if ((_ctx->whatSort == IS_SAY) || (_ctx->whatSort == IS_TALK)) {
3439 				/*
3440 				 * Set bottom of text just above the speaker's head
3441 				 * But don't go off the top of the screen
3442 				 */
3443 				if (TinselV2)
3444 					MultiMoveRelXY(_ctx->pText, 0, _ctx->y - _ctx->Toffset - MultiLowest(_ctx->pText) - 2);
3445 				else {
3446 					yshift = _ctx->y - MultiLowest(_ctx->pText) - 2;		// Just above head
3447 					MultiMoveRelXY(_ctx->pText, 0, yshift);		//
3448 					yshift = MultiHighest(_ctx->pText);
3449 					if (yshift < 4)
3450 						MultiMoveRelXY(_ctx->pText, 0, 4 - yshift);	// Not off top
3451 
3452 					/*
3453 					 * Don't go off the side of the screen
3454 					 */
3455 					xshift = MultiRightmost(_ctx->pText) + 2;
3456 					if (xshift >= SCREEN_WIDTH)			// Not off right
3457 						MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0);
3458 					xshift = MultiLeftmost(_ctx->pText) - 1;
3459 					if (xshift <= 0)					// Not off left
3460 						MultiMoveRelXY(_ctx->pText, -xshift, 0);
3461 				}
3462 			}
3463 
3464 			if (TinselV2)
3465 				// Don't go off the screen
3466 				KeepOnScreen(_ctx->pText, &_ctx->x, &_ctx->y);
3467 
3468 			/*
3469 			 * Work out how long to talk.
3470 			 * During this time, reposition the text if the screen scrolls.
3471 			 */
3472 			_ctx->ticks = TextTime(_vm->_font->TextBufferAddr());
3473 		}
3474 
3475 		if (TinselV2 && _ctx->bSample) {
3476 			// Kick off the sample now (perhaps with a delay)
3477 			if (g_bNoPause)
3478 				g_bNoPause = false;
3479 			else if (!TinselV2Demo)
3480 				CORO_SLEEP(SysVar(SV_SPEECHDELAY));
3481 
3482 			//SamplePlay(VOICE, hText, _ctx->sub, false, -1, -1, PRIORITY_TALK);
3483 			_vm->_sound->playSample(hText, _ctx->sub, false, -1, -1, PRIORITY_TALK, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
3484 		}
3485 
3486 		_ctx->timeout = SAMPLETIMEOUT;
3487 
3488 		do {
3489 			// Keep text in place if scrolling
3490 			if (_ctx->pText != NULL) {
3491 				int	nLoff, nToff;
3492 
3493 				_vm->_bg->PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff);
3494 				if (nLoff != _ctx->Loffset || nToff != _ctx->Toffset) {
3495 					MultiMoveRelXY(_ctx->pText, _ctx->Loffset - nLoff, _ctx->Toffset - nToff);
3496 					_ctx->Loffset = nLoff;
3497 					_ctx->Toffset = nToff;
3498 				}
3499 			}
3500 
3501 			CORO_SLEEP(1);
3502 
3503 			// Handle timeout decrementing and Escape presses
3504 			if (TinselV2) {
3505 				if ((_ctx->escEvents && _ctx->escEvents != GetEscEvents()) ||
3506 					(!bSustain && LeftEventChange(_ctx->myLeftEvent)) ||
3507 					(--_ctx->timeout <= 0)) {
3508 					// Left event only kills current sub-string
3509 					_ctx->myLeftEvent = GetLeftEvents();
3510 					break;
3511 				}
3512 			} else {
3513 				--_ctx->timeout;
3514 
3515 				// Abort if escapable and ESCAPE is pressed
3516 				// Abort if left click - hardwired feature for talk!
3517 				// Abort if sample times out
3518 				if ((escOn && myEscape != GetEscEvents())
3519 						|| (_ctx->myLeftEvent != GetLeftEvents())
3520 						|| (_ctx->timeout <= 0))
3521 					break;
3522 			}
3523 
3524 			if (_ctx->bSample) {
3525 				// Wait for sample to end whether or not
3526 				if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
3527 					if (_ctx->pText == NULL || _vm->_config->_textSpeed == DEFTEXTSPEED) {
3528 						// No text or speed modification - just depends on sample
3529 						break;
3530 					} else {
3531 						// Talk reel stops at end of speech
3532 						if (!TinselV2 || (_ctx->bTalkReel && (_ctx->sub == SubStringCount(hText) - 1))) {
3533 							CORO_INVOKE_2(FinishTalkingReel, _ctx->pActor, _ctx->actor);
3534 							_ctx->bTalkReel = false;
3535 						}
3536 						_ctx->bSample = false;
3537 					}
3538 				}
3539 
3540 				// Decrement the subtitles timeout counter
3541 				if (_ctx->ticks > 0) --_ctx->ticks;
3542 
3543 			} else {
3544 				// No sample - just depends on time
3545 				if (_ctx->ticks-- <= 0)
3546 					break;
3547 			}
3548 		} while (1);
3549 
3550 		if (_ctx->pText != NULL) {
3551 			MultiDeleteObject(_vm->_bg->GetPlayfieldList(FIELD_STATUS), _ctx->pText);
3552 			_ctx->pText = nullptr;
3553 		}
3554 		if (TinselV2 && _ctx->bSample)
3555 			_vm->_sound->stopSpecSample(hText, _ctx->sub);
3556 	}
3557 
3558 	/*
3559 	 * The talk is over now - dump the text
3560 	 * Stop the sample
3561 	 * Restore the actor's film or standing reel
3562 	 */
3563 	if (_ctx->bTalkReel)
3564 		CORO_INVOKE_2(FinishTalkingReel, _ctx->pActor, _ctx->actor);
3565 	if (_ctx->pText != NULL)
3566 		MultiDeleteObject(_vm->_bg->GetPlayfieldList(FIELD_STATUS), _ctx->pText);
3567 
3568 	if (TinselV2) {
3569 		if ((_ctx->whatSort == IS_SAY) || (_ctx->whatSort == IS_SAYAT)) {
3570 			_vm->_actor->SetActorTalking(_ctx->actor, false);
3571 			if (_vm->_actor->IsTaggedActor(_ctx->actor))
3572 				CORO_INVOKE_ARGS(ActorEvent, (CORO_SUBCTX, _ctx->actor, ENDTALK, false, 0));
3573 			else if (IsTagPolygon(_ctx->actor | ACTORTAG_KEY))
3574 				CORO_INVOKE_ARGS(PolygonEvent, (CORO_SUBCTX,
3575 					GetTagHandle(_ctx->actor | ACTORTAG_KEY), ENDTALK, 0, false, 0));
3576 
3577 			CORO_SLEEP(1);
3578 		}
3579 	} else {
3580 		_vm->_mixer->stopHandle(_ctx->handle);
3581 	}
3582 
3583 	/*
3584 	 * Restore user control and tags, as appropriate
3585 	 * And, finally, release the talk token.
3586 	 */
3587 	if (_ctx->bTookControl) {
3588 		if (TinselV2) ControlOn(); else Control(CONTROL_ON);
3589 	}
3590 	if (_ctx->bTookTags)
3591 		EnableTags();
3592 
3593 	CORO_END_CODE;
3594 }
3595 
3596 /**
3597  * TalkAt(actor, x, y, text)
3598  */
TalkAt(CORO_PARAM,int actor,int x,int y,SCNHANDLE text,bool escOn,int myEscape)3599 static void TalkAt(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, bool escOn, int myEscape) {
3600 	if (!coroParam) {
3601 		// Don't do it if it's not wanted
3602 		if (escOn && myEscape != GetEscEvents())
3603 			return;
3604 
3605 		if (!_vm->_config->isJapanMode() && (_vm->_config->_useSubtitles || !_vm->_sound->sampleExists(text)))
3606 			SetTextPal(_vm->_actor->GetActorRGB(actor));
3607 	}
3608 
3609 	Print(coroParam, x, y, text, 0, false, escOn, myEscape);
3610 }
3611 
3612 /**
3613  * TalkAtS(actor, x, y, text, sustain)
3614  */
TalkAtS(CORO_PARAM,int actor,int x,int y,SCNHANDLE text,int sustain,bool escOn,int myEscape)3615 static void TalkAtS(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, int sustain, bool escOn, int myEscape) {
3616 	if (!coroParam) {
3617 		assert(sustain == 2);
3618 
3619 		// Don't do it if it's not wanted
3620 		if (escOn && myEscape != GetEscEvents())
3621 			return;
3622 
3623 		if (!_vm->_config->isJapanMode())
3624 			SetTextPal(_vm->_actor->GetActorRGB(actor));
3625 	}
3626 
3627 	Print(coroParam, x, y, text, 0, sustain == 2, escOn, myEscape);
3628 }
3629 
3630 /**
3631  * Set talk font's palette entry.
3632  */
TalkAttr(int r1,int g1,int b1,bool escOn,int myEscape)3633 static void TalkAttr(int r1, int g1, int b1, bool escOn, int myEscape) {
3634 	if (_vm->_config->isJapanMode())
3635 		return;
3636 
3637 	// Don't do it if it's not wanted
3638 	if (escOn && myEscape != GetEscEvents())
3639 		return;
3640 
3641 	if (r1 > MAX_INTENSITY)	r1 = MAX_INTENSITY;	// } Ensure
3642 	if (g1 > MAX_INTENSITY)	g1 = MAX_INTENSITY;	// } within limits
3643 	if (b1 > MAX_INTENSITY)	b1 = MAX_INTENSITY;	// }
3644 
3645 	SetTextPal(TINSEL_RGB(r1, g1, b1));
3646 }
3647 
3648 /**
3649  * TalkPaletteIndex
3650  */
TalkPaletteIndex(unsigned index)3651 static void TalkPaletteIndex(unsigned index) {
3652 	assert(index);
3653 
3654 	SetTalkTextOffset(index);
3655 }
3656 
3657 /**
3658  * Set talk font's palette entry.
3659  */
TalkRGB(COLORREF color,int myescEvent)3660 static void TalkRGB(COLORREF color, int myescEvent) {
3661 	// Don't do it if it's not wanted
3662 	if (myescEvent && myescEvent != GetEscEvents())
3663 		return;
3664 
3665 	SetTextPal(color);
3666 }
3667 
3668 /**
3669  * TalkVia("actor"/off)
3670  */
TalkVia(int actor)3671 static void TalkVia(int actor) {
3672 	SetSysVar(ISV_DIVERT_ACTOR, actor);
3673 }
3674 
3675 /**
3676  * ThisObject
3677  */
ThisObject(INV_OBJECT * pinvo)3678 static int ThisObject(INV_OBJECT *pinvo) {
3679 	assert(pinvo != NULL);
3680 
3681 	return pinvo->id;
3682 }
3683 
3684 /**
3685  * ThisTag
3686  */
ThisTag(HPOLYGON hp)3687 static int ThisTag(HPOLYGON hp) {
3688 	int tagno;
3689 
3690 	assert(hp != NOPOLY);
3691 
3692 	tagno = GetTagPolyId(hp);
3693 
3694 	assert(IsTagPolygon(tagno));
3695 	assert(tagno);
3696 
3697 	return tagno;
3698 }
3699 
3700 /**
3701  * Get a timer's current count.
3702  */
TimerFn(int timerno)3703 static int TimerFn(int timerno) {
3704 	return Timer(timerno);
3705 }
3706 
3707 /**
3708  * Return the icon that caused the CONVERSE event.
3709  */
Topic()3710 int Topic() {
3711 	return _vm->_dialogs->GetIcon();
3712 }
3713 
3714 /**
3715  * topplay(film, x, y, actor, hold, complete)
3716  */
TopPlay(CORO_PARAM,SCNHANDLE film,int x,int y,int complete,int actorid,bool splay,int sfact,bool escOn,int myescTime)3717 static void TopPlay(CORO_PARAM, SCNHANDLE film, int x, int y, int complete, int actorid, bool splay, int sfact, bool escOn, int myescTime) {
3718 	Play(coroParam, film, x, y, complete, actorid, splay, sfact, escOn, myescTime, true);
3719 }
TopPlay(CORO_PARAM,SCNHANDLE hFilm,int x,int y,bool bComplete,int myescEvent,TINSEL_EVENT event)3720 static void TopPlay(CORO_PARAM, SCNHANDLE hFilm, int x, int y, bool bComplete, int myescEvent, TINSEL_EVENT event) {
3721 	Play(coroParam, hFilm, x, y, bComplete, myescEvent, true, event, NOPOLY, 0);
3722 }
3723 
3724 /**
3725  * Open or close the 'top window'
3726  */
TopWindow(int bpos)3727 static void TopWindow(int bpos) {
3728 	bool isStart = (TinselV2 && (bpos != 0)) || (!TinselV2 && (bpos == TW_START));
3729 
3730 	_vm->_dialogs->KillInventory();
3731 
3732 	if (isStart)
3733 		_vm->_dialogs->OpenMenu(TOP_WINDOW);
3734 }
3735 
3736 /**
3737  * TranslucentIndex
3738  */
TranslucentIndex(unsigned index)3739 static void TranslucentIndex(unsigned index) {
3740 	assert(index <= 255);
3741 
3742 	SetTranslucencyOffset(index);
3743 }
3744 
3745 /**
3746  * Play a sample (DW1 only).
3747  */
TryPlaySample(CORO_PARAM,int sample,bool bComplete,bool escOn,int myEscape)3748 static void TryPlaySample(CORO_PARAM, int sample, bool bComplete, bool escOn, int myEscape) {
3749 	CORO_BEGIN_CONTEXT;
3750 	CORO_END_CONTEXT(_ctx);
3751 
3752 	CORO_BEGIN_CODE(_ctx);
3753 	// Don't do it if it's not appropriate
3754 	if (_vm->_sound->sampleIsPlaying()) {
3755 		// return, but prevent Glitter lock-up
3756 		CORO_SLEEP(1);
3757 		return;
3758 	}
3759 
3760 	CORO_INVOKE_ARGS(PlaySample, (CORO_SUBCTX, sample, bComplete, escOn, myEscape));
3761 	CORO_END_CODE;
3762 }
3763 
3764 /**
3765  * Un-define an actor as tagged.
3766  */
UnTagActorFn(int actor)3767 static void UnTagActorFn(int actor) {
3768 	_vm->_actor->UnTagActor(actor);
3769 }
3770 
3771 /**
3772  * vibrate
3773  */
Vibrate()3774 static void Vibrate() {
3775 }
3776 
3777 /**
3778  * waitframe(int actor, int frameNumber)
3779  */
WaitFrame(CORO_PARAM,int actor,int frameNumber,bool escOn,int myEscape)3780 static void WaitFrame(CORO_PARAM, int actor, int frameNumber, bool escOn, int myEscape) {
3781 	CORO_BEGIN_CONTEXT;
3782 	CORO_END_CONTEXT(_ctx);
3783 
3784 	CORO_BEGIN_CODE(_ctx);
3785 
3786 	while (_vm->_actor->GetActorSteps(actor) < frameNumber) {
3787 		// Don't do it if it's not wanted
3788 		if (escOn && myEscape != GetEscEvents())
3789 			break;
3790 
3791 		CORO_SLEEP(1);
3792 	}
3793 
3794 	CORO_END_CODE;
3795 }
3796 
3797 /**
3798  * Return when a key pressed or button pushed.
3799  */
WaitKey(CORO_PARAM,bool escOn,int myEscape)3800 static void WaitKey(CORO_PARAM, bool escOn, int myEscape) {
3801 	CORO_BEGIN_CONTEXT;
3802 		int	startEvent;
3803 		int startX, startY;
3804 	CORO_END_CONTEXT(_ctx);
3805 
3806 	CORO_BEGIN_CODE(_ctx);
3807 
3808 	// Don't do it if it's not wanted
3809 	if (escOn && myEscape != GetEscEvents())
3810 		return;
3811 
3812 	for (;;) {
3813 		_ctx->startEvent = getUserEvents();
3814 		if (TinselV1) {
3815 			// Store cursor position
3816 			while (!_vm->_cursor->GetCursorXYNoWait(&_ctx->startX, &_ctx->startY, false))
3817 				CORO_SLEEP(1);
3818 		}
3819 
3820 		while (_ctx->startEvent == getUserEvents()) {
3821 			CORO_SLEEP(1);
3822 
3823 			// Not necessary to monitor escape as it's an event anyway
3824 			if (TinselV1) {
3825 				int curX, curY;
3826 				_vm->_cursor->GetCursorXY(&curX, &curY, false); // Store cursor position
3827 				if (curX != _ctx->startX || curY != _ctx->startY)
3828 					break;
3829 			}
3830 
3831 			if (_vm->_dialogs->MenuActive())
3832 				break;
3833 		}
3834 
3835 		if (!_vm->_dialogs->MenuActive())
3836 			return;
3837 
3838 		do {
3839 			CORO_SLEEP(1);
3840 		} while (_vm->_dialogs->MenuActive());
3841 
3842 		CORO_SLEEP(ONE_SECOND / 2);		// Let it die down
3843 	}
3844 	CORO_END_CODE;
3845 }
3846 
3847 /**
3848  * Return when no scrolling is going on.
3849  */
WaitScroll(CORO_PARAM,int myescEvent)3850 void WaitScroll(CORO_PARAM, int myescEvent) {
3851 	CORO_BEGIN_CONTEXT;
3852 		int time;
3853 	CORO_END_CONTEXT(_ctx);
3854 
3855 	CORO_BEGIN_CODE(_ctx);
3856 
3857 	// wait for ongoing scroll
3858 	while (_vm->_scroll->IsScrolling()) {
3859 		if (myescEvent && myescEvent != GetEscEvents())
3860 			break;
3861 
3862 		CORO_SLEEP(1);
3863 	}
3864 
3865 	CORO_END_CODE;
3866 }
3867 
3868 /**
3869  * Pause for requested time.
3870  */
WaitTime(CORO_PARAM,int time,bool frame,bool escOn,int myEscape)3871 static void WaitTime(CORO_PARAM, int time, bool frame, bool escOn, int myEscape) {
3872 	CORO_BEGIN_CONTEXT;
3873 		int time;
3874 	CORO_END_CONTEXT(_ctx);
3875 
3876 	CORO_BEGIN_CODE(_ctx);
3877 
3878 	// Don't do it if it's not wanted
3879 	if (escOn && myEscape != GetEscEvents())
3880 		return;
3881 
3882 	if (!frame)
3883 		time *= ONE_SECOND;
3884 
3885 	_ctx->time = time;
3886 	do {
3887 		CORO_SLEEP(1);
3888 
3889 		// Abort if escapable and ESCAPE is pressed
3890 		if (escOn && myEscape != GetEscEvents())
3891 			break;
3892 	} while (_ctx->time--);
3893 
3894 	CORO_END_CODE;
3895 }
3896 
3897 /**
3898  * Set a moving actor off on a walk.
3899  */
Walk(CORO_PARAM,int actor,int x,int y,SCNHANDLE hFilm,int hold,bool igPath,int zOverride,bool escOn,int myescEvent)3900 void Walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE hFilm, int hold, bool igPath,
3901 		  int zOverride, bool escOn, int myescEvent) {
3902 	CORO_BEGIN_CONTEXT;
3903 		int thisWalk;
3904 	CORO_END_CONTEXT(_ctx);
3905 
3906 	bool bQuick = hold != 0;
3907 	PMOVER pMover = GetMover(actor);
3908 
3909 	assert(pMover); // Can't walk a non-moving actor
3910 
3911 	CORO_BEGIN_CODE(_ctx);
3912 
3913 	// Straight there if escaped
3914 	if (escOn && myescEvent != GetEscEvents()) {
3915 		if (TinselV2)
3916 			StopMover(pMover);
3917 		CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x, y, 0));
3918 		return;
3919 	}
3920 
3921 	if (TinselV2) {
3922 		if (MoverHidden(pMover))
3923 			return;
3924 
3925 		// Test 10/10/96
3926 		while (!MoverIs(pMover))
3927 			CORO_SLEEP(1);
3928 	}
3929 
3930 	assert(pMover->hCpath != NOPOLY); // moving actor not in path
3931 
3932 	// Croak if he is doing an SWalk()
3933 	if (TinselV2) {
3934 		// Croak if he is doing an SWalk()
3935 		if (MoverIsSWalking(pMover))
3936 			CORO_KILL_SELF();
3937 
3938 		_ctx->thisWalk = SetActorDest(pMover, x, y, igPath, hFilm);
3939 		SetMoverZoverride(pMover, zOverride);
3940 		_vm->_scroll->DontScrollCursor();
3941 
3942 		if (!bQuick) {
3943 			while (MoverMoving(pMover)) {
3944 				// Straight there if escaped
3945 				if (escOn && myescEvent != GetEscEvents()) {
3946 					StopMover(pMover);
3947 					CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x, y, 0));
3948 					break;
3949 				}
3950 
3951 				CORO_SLEEP(1);
3952 
3953 				// Die if superceded
3954 				if (_ctx->thisWalk != GetWalkNumber(pMover))
3955 					CORO_KILL_SELF();
3956 			}
3957 		}
3958 	} else {
3959 
3960 		GetToken(pMover->actorToken);
3961 		SetActorDest(pMover, x, y, igPath, hFilm);
3962 		_vm->_scroll->DontScrollCursor();
3963 
3964 		if (hold == 2) {
3965 			;
3966 		} else {
3967 			while (MoverMoving(pMover)) {
3968 				CORO_SLEEP(1);
3969 
3970 				// Straight there if escaped
3971 				if (escOn && myescEvent != GetEscEvents()) {
3972 					CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x, y, 0));
3973 					FreeToken(pMover->actorToken);
3974 					return;
3975 				}
3976 			}
3977 		}
3978 
3979 		FreeToken(pMover->actorToken);
3980 	}
3981 
3982 	CORO_END_CODE;
3983 }
3984 
3985 /**
3986  * Set a moving actor off on a walk.
3987  * Wait to see if its aborted or completed.
3988  */
Walked(CORO_PARAM,int actor,int x,int y,SCNHANDLE film,bool escOn,int myEscape,bool & retVal)3989 static void Walked(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, bool escOn, int myEscape, bool &retVal) {
3990 	// COROUTINE
3991 	CORO_BEGIN_CONTEXT;
3992 		int	thisWalk;
3993 	CORO_END_CONTEXT(_ctx);
3994 
3995 	PMOVER pMover = GetMover(actor);
3996 	assert(pMover); // Can't walk a non-moving actor
3997 
3998 	CORO_BEGIN_CODE(_ctx);
3999 
4000 	// Straight there if escaped
4001 	if (escOn && myEscape != GetEscEvents()) {
4002 		CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x, y, 0));
4003 		retVal = true;
4004 		return;
4005 	}
4006 
4007 	if (TinselV2) {
4008 		if (MoverHidden(pMover) || !MoverIs(pMover)) {
4009 			retVal = false;
4010 			return;
4011 		}
4012 		assert(pMover->hCpath != NOPOLY); // moving actor not in path
4013 
4014 		// Not if he is doing an SWalk()
4015 		if (MoverIsSWalking(pMover)) {
4016 			retVal = false;
4017 			return;
4018 		}
4019 
4020 	} else {
4021 		// Pause before starting the walk
4022 		CORO_SLEEP(ONE_SECOND);
4023 
4024 		assert(pMover->hCpath != NOPOLY); // moving actor not in path
4025 
4026 		// Briefly aquire token to kill off any other normal walk
4027 		GetToken(pMover->actorToken);
4028 		FreeToken(pMover->actorToken);
4029 	}
4030 
4031 	_ctx->thisWalk = SetActorDest(pMover, x, y, false, film);
4032 	_vm->_scroll->DontScrollCursor();
4033 
4034 	while (MoverMoving(pMover) && (_ctx->thisWalk == GetWalkNumber(pMover))) {
4035 		// Straight there if escaped
4036 		if (escOn && myEscape != GetEscEvents()) {
4037 			CORO_INVOKE_ARGS(Stand, (CORO_SUBCTX, actor, x, y, 0));
4038 			retVal = true;
4039 			return;
4040 		}
4041 
4042 		CORO_SLEEP(1);
4043 	}
4044 
4045 	int	endx, endy;
4046 	GetMoverPosition(pMover, &endx, &endy);
4047 	retVal = (_ctx->thisWalk == GetWalkNumber(pMover) && endx == x && endy == y);
4048 
4049 	CORO_END_CODE;
4050 }
4051 
4052 /**
4053  * Declare a moving actor.
4054  */
WalkingActor(uint32 id,SCNHANDLE * rp=NULL)4055 static void WalkingActor(uint32 id, SCNHANDLE *rp = NULL) {
4056 	PMOVER	pActor;		// Moving actor structure
4057 
4058 	if (TinselVersion == TINSEL_V2) {
4059 		RegisterMover(id);
4060 		return;
4061 	}
4062 
4063 	RegisterMover(id);		// Establish as a moving actor
4064 	pActor = GetMover(id);
4065 	assert(pActor);
4066 
4067 	// Store all those reels
4068 	int i, j;
4069 	for (i = 0; i < 5; ++i) {
4070 		for (j = 0; j < 4; ++j)
4071 			pActor->walkReels[i][j] = *rp++;
4072 		for (j = 0; j < 4; ++j)
4073 			pActor->standReels[i][j] = *rp++;
4074 	}
4075 
4076 
4077 	for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) {
4078 		for (j = 0; j < 4; ++j) {
4079 			pActor->walkReels[i][j] = pActor->walkReels[4][j];
4080 			pActor->standReels[i][j] = pActor->standReels[2][j];
4081 		}
4082 	}
4083 }
4084 
4085 /**
4086  * Walk a moving actor towards the polygon's tag, but return when the
4087  * actor enters the polygon.
4088  */
WalkPoly(CORO_PARAM,int actor,SCNHANDLE film,HPOLYGON hp,bool escOn,int myEscape)4089 static void WalkPoly(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myEscape) {
4090 	int	pnodex, pnodey;
4091 
4092 	// COROUTINE
4093 	CORO_BEGIN_CONTEXT;
4094 		int thisWalk;
4095 	CORO_END_CONTEXT(_ctx);
4096 
4097 	assert(hp != NOPOLY); // WalkPoly() may only be called from a polygon code block
4098 	PMOVER pMover = GetMover(actor);
4099 	assert(pMover); // Can't walk a non-moving actor
4100 
4101 	CORO_BEGIN_CODE(_ctx);
4102 
4103 	// Straight there if escaped
4104 	if (escOn && myEscape != GetEscEvents()) {
4105 		StandTag(actor, hp);
4106 		return;
4107 	}
4108 
4109 	if (TinselV2) {
4110 		if (MoverHidden(pMover))
4111 			return;
4112 
4113 		// Croak if he is doing an SWalk()
4114 		if (MoverIsSWalking(pMover))
4115 			CORO_KILL_SELF();
4116 
4117 	} else {
4118 		GetToken(pMover->actorToken);
4119 	}
4120 
4121 	GetPolyNode(hp, &pnodex, &pnodey);
4122 	_ctx->thisWalk = SetActorDest(pMover, pnodex, pnodey, false, film);
4123 	_vm->_scroll->DoScrollCursor();
4124 
4125 	while (!MoverIsInPolygon(pMover, hp) && MoverMoving(pMover)) {
4126 		CORO_SLEEP(1);
4127 
4128 		if (escOn && myEscape != GetEscEvents()) {
4129 			// Straight there if escaped
4130 			StandTag(actor, hp);
4131 			if (!TinselV2)
4132 				FreeToken(pMover->actorToken);
4133 			return;
4134 		}
4135 
4136 		// Die if superceded
4137 		if (TinselV2 && (_ctx->thisWalk != GetWalkNumber(pMover)))
4138 			CORO_KILL_SELF();
4139 	}
4140 
4141 	if (!TinselV2)
4142 		FreeToken(pMover->actorToken);
4143 
4144 	CORO_END_CODE;
4145 }
4146 
4147 /**
4148  * WalkTag(actor, reel, hold)
4149  */
WalkTag(CORO_PARAM,int actor,SCNHANDLE film,HPOLYGON hp,bool escOn,int myEscape)4150 static void WalkTag(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myEscape) {
4151 	// COROUTINE
4152 	CORO_BEGIN_CONTEXT;
4153 		int thisWalk;
4154 	CORO_END_CONTEXT(_ctx);
4155 
4156 	PMOVER pMover = GetMover(actor);
4157 	assert(pMover); // Can't walk a non-moving actor
4158 
4159 	CORO_BEGIN_CODE(_ctx);
4160 
4161 	int	pnodex, pnodey;
4162 
4163 	assert(hp != NOPOLY); // walkpoly() may only be called from a polygon code block
4164 
4165 	// Straight there if escaped
4166 	if (escOn && myEscape != GetEscEvents()) {
4167 		StandTag(actor, hp);
4168 		return;
4169 	}
4170 
4171 	if (!TinselV2)
4172 		GetToken(pMover->actorToken);
4173 	else {
4174 		if (MoverHidden(pMover))
4175 			return;
4176 	}
4177 
4178 	GetPolyNode(hp, &pnodex, &pnodey);
4179 
4180 	_ctx->thisWalk = SetActorDest(pMover, pnodex, pnodey, false, film);
4181 	_vm->_scroll->DoScrollCursor();
4182 
4183 	while (MoverMoving(pMover)) {
4184 		if (escOn && myEscape != GetEscEvents()) {
4185 			// Straight there if escaped
4186 			StandTag(actor, hp);
4187 			if (!TinselV2)
4188 				FreeToken(pMover->actorToken);
4189 			return;
4190 		}
4191 
4192 		CORO_SLEEP(1);
4193 
4194 		// Die if superceded
4195 		if (TinselV2 && (_ctx->thisWalk != GetWalkNumber(pMover)))
4196 			CORO_KILL_SELF();
4197 	}
4198 
4199 	// Adopt the tag-related reel
4200 	SCNHANDLE pFilm = GetPolyFilm(hp);
4201 
4202 	switch (pFilm) {
4203 	case TF_NONE:
4204 		break;
4205 
4206 	case TF_UP:
4207 		SetMoverDirection(pMover, AWAY);
4208 		SetMoverStanding(pMover);
4209 		break;
4210 	case TF_DOWN:
4211 		SetMoverDirection(pMover, FORWARD);
4212 		SetMoverStanding(pMover);
4213 		break;
4214 	case TF_LEFT:
4215 		SetMoverDirection(pMover, LEFTREEL);
4216 		SetMoverStanding(pMover);
4217 		break;
4218 	case TF_RIGHT:
4219 		SetMoverDirection(pMover, RIGHTREEL);
4220 		SetMoverStanding(pMover);
4221 		break;
4222 
4223 	default:
4224 		if (actor == LEAD_ACTOR || actor == _vm->_actor->GetLeadId())
4225 			AlterMover(pMover, pFilm, AR_NORMAL);
4226 		else
4227 			SetMoverStanding(pMover);
4228 		break;
4229 	}
4230 
4231 	if (!TinselV2)
4232 		FreeToken(pMover->actorToken);
4233 
4234 	CORO_END_CODE;
4235 }
4236 
4237 /**
4238  * Returns the X co-ordinateof lead actor's last walk.
4239  */
WalkXPos()4240 int WalkXPos() {
4241 	return GetLastLeadXdest();
4242 }
4243 
4244 /**
4245  * Returns the Y co-ordinateof lead actor's last walk.
4246  */
WalkYPos()4247 int WalkYPos() {
4248 	return GetLastLeadYdest();
4249 }
4250 
4251 /**
4252  * Return which is the current CD, counting from 1.
4253  */
WhichCd()4254 int WhichCd() {
4255 	return GetCurrentCD();
4256 }
4257 
4258 /**
4259  * whichinventory
4260  */
WhichInventory()4261 int WhichInventory() {
4262 	return _vm->_dialogs->WhichInventoryOpen();
4263 }
4264 
4265 
4266 struct NoirMapping {
4267 	const char *name;
4268 	int libCode;
4269 	int numArgs;
4270 };
4271 
translateNoirLibCode(int libCode,int32 * pp)4272 NoirMapping translateNoirLibCode(int libCode, int32 *pp) {
4273 	// This function allows us to both log the called library functions, as well
4274 	// as to stub the ones we haven't yet implemented. Eventually this might
4275 	// get rolled up into a lookup table similar to DW1 and DW2, but for now
4276 	// this is convenient for debug.
4277 	NoirMapping mapping;
4278 	switch (libCode) {
4279 	case 3:
4280 		mapping = NoirMapping{"ACTORPRIORITY", ACTORPRIORITY, 2};
4281 		pp -= mapping.numArgs - 1;
4282 		debug(7, "%s(%d, 0x%08X)", mapping.name, pp[0], pp[1]);
4283 		break;
4284 	case 5:
4285 		mapping = NoirMapping{"ACTORRGB", ACTORRGB, 2};
4286 		pp -= mapping.numArgs - 1;
4287 		debug(7, "%s(%d, 0x%08X)", mapping.name, pp[0], pp[1]);
4288 		break;
4289 	case 8:
4290 		mapping = NoirMapping{"ADDNOTEBOOK", ADDNOTEBOOK, 1};
4291 		pp -= mapping.numArgs - 1;
4292 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4293 		break;
4294 	case 9:
4295 		mapping = NoirMapping{"ADDCONV", ADDCONV, 1};
4296 		pp -= mapping.numArgs - 1;
4297 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4298 		break;
4299 	case 12:
4300 		mapping = NoirMapping{"ADDINV1", ADDINV1, 1};
4301 		pp -= mapping.numArgs - 1;
4302 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4303 		break;
4304 	case 16:
4305 		mapping = NoirMapping{"ADDINV3", ADDINV3, 1};
4306 		pp -= mapping.numArgs - 1;
4307 		debug(7, "%s(%08X)", mapping.name, pp[0]);
4308 		break;
4309 	case 18:
4310 		mapping = NoirMapping{"BACKGROUND", BACKGROUND, 1};
4311 		pp -= mapping.numArgs - 1;
4312 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4313 		break;
4314 	case 37:
4315 		mapping = NoirMapping{"CONTROL", CONTROL, 1};
4316 		pp -= mapping.numArgs - 1;
4317 		debug(7, "%s(%08X)", mapping.name, pp[0]);
4318 		break;
4319 	case 26:
4320 		mapping = NoirMapping{"CALLTAG", CALLTAG, 2};
4321 		pp -= mapping.numArgs - 1;
4322 		debug(7, "%s(0x%08X, 0x%08X)", mapping.name, pp[0], pp[1]);
4323 		break;
4324 	case 28:
4325 		mapping = NoirMapping{"CDCHANGESCENE", CDCHANGESCENE, 1};
4326 		pp -= mapping.numArgs - 1;
4327 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4328 		break;
4329 	case 41:
4330 		mapping = NoirMapping{"CURSORXPOS", CURSORXPOS, 0};
4331 		debug(7, "%s()", mapping.name);
4332 		break;
4333 	case 42:
4334 		mapping = NoirMapping{"CURSORYPOS", CURSORYPOS, 0};
4335 		debug(7, "%s()", mapping.name);
4336 		break;
4337 	case 43:
4338 		mapping = NoirMapping{"DECINVMAIN", DECINVMAIN, 8};
4339 		pp -= mapping.numArgs - 1;
4340 		debug(7, "%s(0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X)", mapping.name, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6], pp[7]);
4341 		break;
4342 	case 44: // Changed in Noir
4343 		mapping = NoirMapping{"DECINV2", DECINV2, 8};
4344 		pp -= mapping.numArgs - 1;
4345 		debug(7, "%s(0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X)", mapping.name, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6], pp[7]);
4346 		break;
4347 	case 45:
4348 		mapping = NoirMapping{"DECLARELANGUAGE", DECLARELANGUAGE, 3};
4349 		pp -= mapping.numArgs - 1;
4350 		debug(7, "%s(%d, 0x%08X, 0x%08X)", mapping.name, pp[0], pp[1], pp[2]);
4351 		break;
4352 	case 46:
4353 		mapping = NoirMapping{"DECLEAD", DECLEAD, 1};
4354 		pp -= mapping.numArgs - 1;
4355 		debug(7, "%s(%d)", mapping.name, pp[0]);
4356 		break;
4357 	case 47:
4358 		mapping = NoirMapping{"DEC3D", DEC3D, 1};
4359 		pp -= mapping.numArgs - 1;
4360 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4361 		break;
4362 	case 48:
4363 		mapping = NoirMapping{"DECTAGFONT", DECTAGFONT, 1};
4364 		pp -= mapping.numArgs - 1;
4365 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4366 		break;
4367 	case 49:
4368 		mapping = NoirMapping{"DECTALKFONT", DECTALKFONT, 1};
4369 		pp -= mapping.numArgs - 1;
4370 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4371 		break;
4372 	case 58:
4373 		mapping = NoirMapping{"ENDACTOR", ENDACTOR, 1};
4374 		pp -= mapping.numArgs - 1;
4375 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4376 		break;
4377 	case 61:
4378 		mapping = NoirMapping{"EVENT", EVENT, 0};
4379 		debug(7, "%s()", mapping.name);
4380 		break;
4381 	case 64:
4382 		mapping = NoirMapping{"FADEMUSIC", FADEMUSIC, 2};
4383 		pp -= mapping.numArgs - 1;
4384 		debug(7, "%s(%08X, %d)", mapping.name, pp[0], pp[1]);
4385 		break;
4386 	case 74:
4387 		mapping = NoirMapping{"HAVE", HAVE, 1};
4388 		pp -= mapping.numArgs - 1;
4389 		debug(7, "%s(%d)", mapping.name, pp[0]);
4390 		break;
4391 	case 77:
4392 		mapping = NoirMapping{"HIDEACTOR", HIDEACTOR, 1};
4393 		pp -= mapping.numArgs - 1;
4394 		debug(7, "%s(%d)", mapping.name, pp[0]);
4395 		break;
4396 	case 83:
4397 		mapping = NoirMapping{"HIDETAG", HIDETAG, 1};
4398 		pp -= mapping.numArgs - 1;
4399 		debug(7, "%s(%d)", mapping.name, pp[0]);
4400 		break;
4401 	case 86:
4402 		mapping = NoirMapping{"OP86", ZZZZZZ, 2};
4403 		pp -= mapping.numArgs - 1;
4404 		debug(7, "%s(0x%08X, 0x%08X)", mapping.name, pp[0], pp[1]);
4405 		break;
4406 	case 94:
4407 		mapping = NoirMapping{"KILLPROCESS", KILLPROCESS, 1};
4408 		pp -= mapping.numArgs - 1;
4409 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4410 		break;
4411 	case 96:
4412 		mapping = NoirMapping{"MOVECURSOR", MOVECURSOR, 2};
4413 		pp -= mapping.numArgs - 1;
4414 		debug(7, "%s(%d, %d)", mapping.name, pp[0], pp[1]);
4415 		break;
4416 	case 99:
4417 		mapping = NoirMapping{"NEWSCENE", NEWSCENE, 3};
4418 		pp -= mapping.numArgs - 1;
4419 		debug(7, "%s(0x%08X, 0x%08X, 0x%08X)", mapping.name, pp[0], pp[1], pp[2]);
4420 		break;
4421 	case 110:
4422 		mapping = NoirMapping{"PLAY", PLAY, 2};
4423 		pp -= mapping.numArgs - 1;
4424 		debug(7, "%s(0x%08X, 0x%08X)", mapping.name, pp[0], pp[1]);
4425 		break;
4426 	case 112:
4427 		mapping = NoirMapping{"PLAYMUSIC", PLAYMUSIC, 1};
4428 		pp -= mapping.numArgs - 1;
4429 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4430 		break;
4431 	case 113:
4432 		mapping = NoirMapping{"PLAYSAMPLE", PLAYSAMPLE, 4};
4433 		pp -= mapping.numArgs - 1;
4434 		debug(7, "%s(0x%08X, 0x%08X, 0x%08X, 0x%08X)", mapping.name, pp[0], pp[1], pp[2], pp[3]);
4435 		break;
4436 	case 114:
4437 		mapping = NoirMapping{"POINTACTOR", POINTACTOR, 1};
4438 		pp -= mapping.numArgs - 1;
4439 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4440 		break;
4441 	case 121:
4442 		mapping = NoirMapping{"POSTTAG", POSTTAG, 2};
4443 		pp -= mapping.numArgs - 1;
4444 		debug(7, "%s(0x%08X, 0x%08X)", mapping.name, pp[0], pp[1]);
4445 		break;
4446 	case 124:
4447 		mapping = NoirMapping{"PRINTCURSOR", PRINTCURSOR, 1};
4448 		pp -= mapping.numArgs - 1;
4449 		debug(7, "%s(%d)", mapping.name, pp[0]);
4450 		break;
4451 	case 126:
4452 		mapping = NoirMapping{"PRINTTAG", PRINTTAG, 1};
4453 		pp -= mapping.numArgs - 1;
4454 		debug(7, "%s(%d)", mapping.name, pp[0]);
4455 		break;
4456 	case 128:
4457 		mapping = NoirMapping{"RANDOM", RANDOM, 3};
4458 		pp -= mapping.numArgs - 1;
4459 		debug(7, "%s(%d, %d, %d)", mapping.name, pp[0], pp[1], pp[2]);
4460 		break;
4461 	case 135:
4462 	case 221:
4463 	case 222:
4464 	case 223:
4465 		mapping = NoirMapping{"SAY", SAY, 2};
4466 		pp -= mapping.numArgs - 2;
4467 		debug(7, "%s_%2Xh(%d, %d)", mapping.name, libCode, pp[0], pp[1]);
4468 		break;
4469 	case 151:
4470 		mapping = NoirMapping{"SETSYSTEMREEL", SETSYSTEMREEL, 2};
4471 		pp -= mapping.numArgs - 1;
4472 		debug(7, "%s(%d, 0x%08X)", mapping.name, pp[0], pp[1]);
4473 		break;
4474 	case 152:
4475 		mapping = NoirMapping{"SETSYSTEMSTRING", SETSYSTEMSTRING, 2};
4476 		pp -= mapping.numArgs - 1;
4477 		debug(7, "%s(%d, %08X)", mapping.name, pp[0], pp[1]);
4478 		break;
4479 	case 153:
4480 		mapping = NoirMapping{"SETSYSTEMVAR", SETSYSTEMVAR, 2};
4481 		pp -= mapping.numArgs - 1;
4482 		debug(7, "%s(%d, 0x%08X)", mapping.name, pp[0], pp[1]);
4483 		break;
4484 	case 154:
4485 		mapping = NoirMapping{"SETVIEW", SETVIEW, 2};
4486 		pp -= mapping.numArgs - 1;
4487 		debug(7, "%s(%d, 0x%08X)", mapping.name, pp[0], pp[1]);
4488 		break;
4489 	case 156:
4490 		mapping = NoirMapping{"SHOWACTOR", SHOWACTOR, 1};
4491 		pp -= mapping.numArgs - 1;
4492 		debug(7, "%s(%d)", mapping.name, pp[0]);
4493 		break;
4494 	case 159:
4495 		mapping = NoirMapping{"SHOWMENU", SHOWMENU, 0};
4496 		debug(7, "%s()", mapping.name);
4497 		break;
4498 	case 163:
4499 		mapping = NoirMapping{"SHOWTAG", SHOWTAG, 1};
4500 		pp -= mapping.numArgs - 1;
4501 		debug(7, "%s(%d)", mapping.name, pp[0]);
4502 		break;
4503 	case 164:
4504 		mapping = NoirMapping{"STAND", STAND, 4};
4505 		pp -= mapping.numArgs - 1;
4506 		debug(7, "%s(0x%08X, 0x%08X, 0x%08X, 0x%08X)", mapping.name, pp[0], pp[1], pp[2], pp[3]);
4507 		break;
4508 	case 167:
4509 		mapping = NoirMapping{"STARTPROCESS", STARTPROCESS, 1};
4510 		pp -= mapping.numArgs - 1;
4511 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4512 		break;
4513 	case 175:
4514 		mapping = NoirMapping{"SYSTEMVAR", SYSTEMVAR, 0};
4515 		debug(7, "%s(%d)", mapping.name, pp[0]);
4516 		break;
4517 	case 183:
4518 		mapping = NoirMapping{"TALKVIA", TALKVIA, 1};
4519 		pp -= mapping.numArgs - 1;
4520 		debug(7, "%s(%d)", mapping.name, pp[0]);
4521 		break;
4522 	case 197:
4523 		mapping = NoirMapping{"WAITTIME", WAITTIME, 2};
4524 		pp -= mapping.numArgs - 1;
4525 		debug(7, "%s(%d, %d)", mapping.name, pp[0], pp[1]);
4526 		break;
4527 	case 207:
4528 		mapping = NoirMapping{"WHICHCD", WHICHCD, 0};
4529 		debug(7, "%s()", mapping.name);
4530 		break;
4531 	case 208:
4532 		mapping = NoirMapping{"WHICHINVENTORY", WHICHINVENTORY, 0};
4533 		debug(7, "%s()", mapping.name);
4534 		break;
4535 	case 210: // STUBBED
4536 		mapping = NoirMapping{"OP210", ZZZZZZ, 8};
4537 		pp -= mapping.numArgs - 1;
4538 		debug(7, "%s(0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X)", mapping.name, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6], pp[7]);
4539 		break;
4540 	case 212: // STUBBED
4541 		mapping = NoirMapping{"OP212", ZZZZZZ, 8};
4542 		pp -= mapping.numArgs - 1;
4543 		debug(7, "%s(0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X)", mapping.name, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6], pp[7]);
4544 		break;
4545 	case 213: // STUBBED
4546 		mapping = NoirMapping{"OP213", ZZZZZZ, 8};
4547 		pp -= mapping.numArgs - 1;
4548 		debug(7, "%s(0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X)", mapping.name, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6], pp[7]);
4549 		break;
4550 	case 214:
4551 		mapping = NoirMapping{"SET3DTEXTURE", SET3DTEXTURE, 1};
4552 		pp -= mapping.numArgs - 1;
4553 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4554 		break;
4555 	case 217:
4556 		mapping = NoirMapping{"VOICEOVER", VOICEOVER, 1};
4557 		pp -= mapping.numArgs - 1;
4558 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4559 		break;
4560 	case 111: // no hold frame
4561 	case 225: // hold frame
4562 		mapping = NoirMapping{"PLAYMOVIE", PLAYMOVIE, 1};
4563 		pp -= mapping.numArgs - 1;
4564 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4565 		break;
4566 	case 228: // STUBBED
4567 		mapping = NoirMapping{"OP228", ZZZZZZ, 1};
4568 		pp -= mapping.numArgs - 1;
4569 		debug(7, "%s(0x%08X)", mapping.name, pp[0]);
4570 		break;
4571 	default:
4572 		error("Unmapped libCode %d", libCode);
4573 	}
4574 
4575 	return mapping;
4576 }
4577 
4578 
4579 /**
4580  * Subtract one less that the number of parameters from pp
4581  * pp then points to the first parameter.
4582  *
4583  * If the library function has no return value:
4584  * return -(the number of parameters) to pop them from the stack
4585  *
4586  * If the library function has a return value:
4587  * return -(the number of parameters - 1) to pop most of them from
4588  * the stack, and stick the return value in pp[0]
4589  * @param operand			Library function
4590  * @param pp				Top of parameter stack
4591  */
CallLibraryRoutine(CORO_PARAM,int operand,int32 * pp,const INT_CONTEXT * pic,RESUME_STATE * pResumeState)4592 int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pic, RESUME_STATE *pResumeState) {
4593 	int libCode;
4594 	if (TinselV0) libCode = DW1DEMO_CODES[operand];
4595 	else if (!TinselV2) libCode = DW1_CODES[operand];
4596 	else if (TinselV2Demo) libCode = DW2DEMO_CODES[operand];
4597 	else if (TinselV3) {
4598 		NoirMapping mapping = translateNoirLibCode(operand, pp);
4599 		libCode = mapping.libCode;
4600 		if (libCode == ZZZZZZ) {
4601 			debug(7, "%08X CallLibraryRoutine op %d (escOn %d, myEscape %d)", pic->hCode, operand, pic->escOn, pic->myEscape);
4602 			return -mapping.numArgs;
4603 		}
4604 	}
4605 	else libCode = DW2_CODES[operand];
4606 
4607 	debug(7, "CallLibraryRoutine op %d (escOn %d, myEscape %d)", operand, pic->escOn, pic->myEscape);
4608 	switch (libCode) {
4609 	case ACTORATTR:
4610 		// DW1 only
4611 		pp -= 3;			// 4 parameters
4612 		ActorAttr(pp[0], pp[1], pp[2], pp[3]);
4613 		return -4;
4614 
4615 	case ACTORBRIGHTNESS:
4616 		// DW2 only
4617 		pp -= 1;
4618 		ActorBrightness(pp[0], pp[1]);
4619 		return -2;
4620 
4621 	case ACTORDIRECTION:
4622 		// Common to both DW1 & DW2
4623 		pp[0] = ActorDirection(pp[0]);
4624 		return 0;
4625 
4626 	case ACTORPALETTE:
4627 		// DW2 only
4628 		pp -= 2;			// 3 parameters
4629 		ActorPalette(pp[0], pp[1], pp[2]);
4630 		return -3;
4631 
4632 	case ACTORPRIORITY:
4633 		// DW2 / Noir
4634 		pp -= 1;			// 2 parameters
4635 		ActorPriority(pp[0], pp[1]);
4636 		return -2;
4637 
4638 	case ACTORREF:
4639 		// Common to both DW1 & DW2
4640 		if (!TinselV0)
4641 			error("actorref isn't a real function");
4642 		return 0;
4643 
4644 	case ACTORRGB:
4645 		// Common to DW2 / Noir
4646 		pp -= 1;			// 2 parameters
4647 		ActorRGB(pp[0], pp[1]);
4648 		return -2;
4649 
4650 	case ACTORSCALE:
4651 		// Common to both DW1 & DW2
4652 		pp[0] = ActorScale(pp[0]);
4653 		return 0;
4654 
4655 	case ACTORSON:
4656 		// DW1 only
4657 		ActorsOn();
4658 		return 0;
4659 
4660 	case ACTORXPOS:
4661 		// Common to both DW1 & DW2
4662 		pp[0] = ActorPos(ACTORXPOS, pp[0]);
4663 		return 0;
4664 
4665 	case ACTORYPOS:
4666 		// Common to both DW1 & DW2
4667 		pp[0] = ActorPos(ACTORYPOS, pp[0]);
4668 		return 0;
4669 
4670 	case ADDCONV:
4671 		// Noir only
4672 		warning("TODO: Implement ADDCONV");
4673 		return -1;
4674 
4675 	case ADDHIGHLIGHT:
4676 		// DW2 only
4677 		// Command doesn't actually do anything
4678 		pp -= 1;			// 2 parameters
4679 		return -2;
4680 
4681 	case ADDINV:
4682 		// DW2 only
4683 		AddInv(INV_DEFAULT, pp[0]);
4684 		return -1;
4685 
4686 	case ADDINV1:
4687 		// Common to DW1 / DW2 / Noir
4688 		AddInv(INV_1, pp[0]);
4689 		return -1;
4690 
4691 	case ADDINV2:
4692 		// Common to both DW1 & DW2
4693 		AddInv(INV_2, pp[0]);
4694 		return -1;
4695 
4696 	case ADDINV3:
4697 		// Noir only
4698 		warning("TODO: Implement ADDINV3");
4699 		return -1;
4700 
4701 	case ADDNOTEBOOK:
4702 		// Noir Only
4703 		warning("TODO: Implement ADDNOTEBOOK");
4704 		return -1;
4705 
4706 	case ADDOPENINV:
4707 		// Common to both DW1 & DW2
4708 		AddInv(TinselV2 ? DW2_INV_OPEN : INV_OPEN, pp[0]);
4709 		return -1;
4710 
4711 	case ADDTOPIC:
4712 		// Common to both DW1 & DW2
4713 		AddTopic(pp[0]);
4714 		return -1;
4715 
4716 	case AUXSCALE:
4717 		// DW1 only
4718 		pp -= 13;			// 14 parameters
4719 		AuxScale(pp[0], pp[1], (SCNHANDLE *)(pp+2));
4720 		return -14;
4721 
4722 	case BACKGROUND:
4723 		// Common to DW1 / DW2 / Noir
4724 		startBackground(coroParam, pp[0]);
4725 		return -1;
4726 
4727 	case BLOCKING:
4728 		// DW2 only
4729 		Blocking(pp[0]);
4730 		return -1;
4731 
4732 	case CALLACTOR:
4733 	case CALLGLOBALPROCESS:
4734 	case CALLOBJECT:
4735 		// DW2 only
4736 		pp -= 1;			// 2 parameters
4737 		if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
4738 			bool result;
4739 			*pResumeState = RES_NOT;
4740 			FinishWaiting(coroParam, pic, &result);
4741 			if (coroParam) {
4742 				*pResumeState = RES_1;
4743 				return 0;
4744 			}
4745 			pp[0] = result ? 1 : 0;
4746 		} else {
4747 			uint32 v;
4748 			if (libCode == CALLACTOR)
4749 				v = SendActor(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->hPoly, pic->myEscape);
4750 			else if (libCode == CALLGLOBALPROCESS)
4751 				v = SendGlobalProcess(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape);
4752 			else
4753 				v = SendObject(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape);
4754 
4755 			if (coroParam)
4756 				return 0;
4757 			pp[0] = v;
4758 		}
4759 
4760 		if (!pp[0])
4761 			KillSelf(coroParam);
4762 		return -2;
4763 
4764 
4765 	case CALLPROCESS:
4766 		// DW2 only
4767 		pp -= 1;			// 2 parameters
4768 		if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
4769 			bool result;
4770 			*pResumeState = RES_NOT;
4771 			FinishWaiting(coroParam, pic, &result);
4772 			if (coroParam) {
4773 				*pResumeState = RES_1;
4774 				return 0;
4775 			}
4776 
4777 			pp[0] = result ? 1 : 0;
4778 		} else {
4779 			int result = SendProcess(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape);
4780 			if (coroParam)
4781 				return 0;
4782 
4783 			pp[0] = result;
4784 		}
4785 		return -2;
4786 
4787 	case CALLSCENE:
4788 		// DW2 only
4789 		error("CallScene isn't a real function");
4790 
4791 	case CALLTAG:
4792 		// DW2 / Noir
4793 		pp -= 1;			// 2 parameters
4794 		if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
4795 			bool result;
4796 			*pResumeState = RES_NOT;
4797 			FinishWaiting(coroParam, pic, &result);
4798 			if (coroParam) {
4799 				*pResumeState = RES_1;
4800 				return 0;
4801 			}
4802 
4803 			pp[0] = result ? 1 : 0;
4804 		} else {
4805 			bool result;
4806 			SendTag(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->hPoly, pic->myEscape, &result);
4807 			if (coroParam)
4808 				return 0;
4809 
4810 			pp[0] = result ? 1 : 0;
4811 		}
4812 
4813 		if (!pp[0])
4814 			KillSelf(coroParam);
4815 		return -2;
4816 
4817 	case CAMERA:
4818 		// Common to both DW1 & DW2
4819 		Camera(pp[0]);
4820 		return -1;
4821 
4822 	case CDCHANGESCENE:
4823 		// DW2 / Noir
4824 		CdChangeScene(pp[0]);
4825 		return -1;
4826 
4827 	case CDDOCHANGE:
4828 		// DW2 only
4829 		CdDoChange(coroParam);
4830 		return 0;
4831 
4832 	case CDENDACTOR:
4833 		// DW2 only
4834 		CdEndActor(pp[0], pic->myEscape);
4835 		return -1;
4836 
4837 	case CDLOAD:
4838 		// Common to both DW1 & DW2
4839 		pp -= 1;			// 2 parameters
4840 		CDload(pp[0], pp[1], pic->myEscape);
4841 		return -2;
4842 
4843 	case CDPLAY:
4844 		// Common to both DW1 & DW2
4845 		error("cdplay isn't a real function");
4846 
4847 	case CLEARHOOKSCENE:
4848 		// Common to both DW1 & DW2
4849 		ClearHookScene();
4850 		return 0;
4851 
4852 	case CLOSEINVENTORY:
4853 		// Common to both DW1 & DW2
4854 		CloseInventory();
4855 		return 0;
4856 
4857 	case CONTROL:
4858 		// Common to DW1 / DW2 / Noir
4859 		Control(pp[0]);
4860 		return -1;
4861 
4862 	case CONVERSATION:
4863 		// Common to both DW1 & DW2
4864 		Conversation(coroParam, pp[0], pic->hPoly, pic->idActor, pic->escOn, pic->myEscape);
4865 		return -1;
4866 
4867 	case CONVTOPIC:
4868 		// Common to both DW1 & DW2
4869 		ConvTopic(pp[0]);
4870 		return -1;
4871 
4872 	case CURSOR:
4873 		// DW2 only
4874 		ToggleCursor(pp[0]);
4875 		return -1;
4876 
4877 	case CURSORXPOS:
4878 		// Common to DW1 / DW2 / Noir
4879 		pp[0] = CursorPos(CURSORXPOS);
4880 		return 0;
4881 
4882 	case CURSORYPOS:
4883 		// Common to DW1 / DW2 / Noir
4884 		pp[0] = CursorPos(CURSORYPOS);
4885 		return 0;
4886 
4887 	case CUTSCENE:
4888 		// DW1 only
4889 		error("cutscene isn't a real function");
4890 
4891 	case DEC3D:
4892 		// Noir only
4893 		pp -= 2;
4894 		Dec3D(pp[0], pp[1], pp[2]);
4895 		return -3;
4896 
4897 	case DECCONVW:
4898 		// Common to both DW1 & DW2
4899 		pp -= 7;			// 8 parameters
4900 		DecConvW(pp[0], pp[1], pp[2], pp[3],
4901 			 pp[4], pp[5], pp[6], pp[7]);
4902 		return -8;
4903 
4904 	case DECCSTRINGS:
4905 		// DW1 only
4906 		pp -= 19;			// 20 parameters
4907 		DecCStrings((SCNHANDLE *)pp);
4908 		return -20;
4909 
4910 	case DECCURSOR:
4911 		// Common to both DW1 & DW2
4912 		DecCursor(pp[0]);
4913 		return -1;
4914 
4915 	case DECFLAGS:
4916 		// Common to both DW1 & DW2
4917 		if (TinselV2)
4918 			error("DecFlags() is obsolete");
4919 
4920 		DecFlags(pp[0]);
4921 		return -1;
4922 
4923 	case DECINV1:
4924 		// Common to both DW1 & DW2
4925 		pp -= 7;			// 8 parameters
4926 		DecInv1(pp[0], pp[1], pp[2], pp[3],
4927 			 pp[4], pp[5], pp[6], pp[7]);
4928 		return -8;
4929 
4930 	case DECINV2:
4931 		// Common to DW1 / DW2 / Noir
4932 		pp -= 7;			// 8 parameters
4933 		DecInv2(pp[0], pp[1], pp[2], pp[3],
4934 			 pp[4], pp[5], pp[6], pp[7]);
4935 		return -8;
4936 
4937 	case DECINVMAIN:
4938 		warning("TODO: Implement DECINVMAIN");
4939 		return -8;
4940 
4941 	case DECINVW:
4942 		// Common to both DW1 & DW2
4943 		DecInvW(pp[0]);
4944 		return -1;
4945 
4946 	case DECLARELANGUAGE:
4947 		// Common to DW2 & Noir
4948 		pp -= 2;			// 3 parameters
4949 		DeclareLanguage(pp[0], pp[1], pp[2]);
4950 		return -3;
4951 
4952 	case DECLEAD:
4953 		// Common to DW1 / DW2 / Noir
4954 		if (TinselV2) {
4955 			DecLead(pp[0]);
4956 			return -1;
4957 		} else {
4958 			pp -= 61;			// 62 parameters
4959 			DecLead(pp[0], (SCNHANDLE *)&pp[1], pp[61]);
4960 			return -62;
4961 		}
4962 
4963 	case DECSCALE:
4964 		// DW2 only
4965 		pp -= 13;			// 14 parameters
4966 		DecScale(pp[0], pp[1], pp[2], pp[3], pp[4],
4967 			 pp[5], pp[6], pp[7], pp[8], pp[9],
4968 			 pp[10], pp[11], pp[12], pp[13]);
4969 		return -14;
4970 
4971 	case DECTAGFONT:
4972 		// Common to DW1 / DW2 / Noir
4973 		_vm->_font->SetTagFontHandle(pp[0]);
4974 		return -1;
4975 
4976 	case DECTALKFONT:
4977 		// Common to DW1 / DW2 / Noir
4978 		_vm->_font->SetTalkFontHandle(pp[0]);
4979 		return -1;
4980 
4981 	case DELICON:
4982 		// DW1 only
4983 		DelIcon(pp[0]);
4984 		return -1;
4985 
4986 	case DELINV:
4987 		// DW1 only
4988 		DelInv(pp[0]);
4989 		return -1;
4990 
4991 	case DELTOPIC:
4992 		// DW2 only
4993 		DelTopic(pp[0]);
4994 		return -1;
4995 
4996 	case DIMMUSIC:
4997 		// DW2 only
4998 		_vm->_pcmMusic->dim(true);
4999 		return 0;
5000 
5001 	case DROP:
5002 		// DW2 only
5003 		Drop(pp[0]);
5004 		return -1;
5005 
5006 	case DROPEVERYTHING:
5007 		// DW2 only
5008 		DropEverything();
5009 		return 0;
5010 
5011 	case DROPOUT:
5012 		// DW1 only
5013 		error("DropOut (%d)", pp[0]);
5014 
5015 	case EFFECTACTOR:
5016 		// Common to both DW1 & DW2
5017 		assert(pic->event == WALKIN || pic->event == WALKOUT); // effectactor() must be from effect poly code
5018 
5019 		pp[0] = pic->idActor;
5020 		return 0;
5021 
5022 	case ENABLEMENU:
5023 		// Common to both DW1 & DW2
5024 		EnableMenu();
5025 		return 0;
5026 
5027 	case ENDACTOR:
5028 		// DW2 & Noir
5029 		EndActor(pp[0]);
5030 		return -1;
5031 
5032 	case ESCAPE:
5033 	case ESCAPEOFF:
5034 	case ESCAPEON:
5035 		// Common to both DW1 & DW2
5036 		error("Escape isn't a real function");
5037 
5038 	case EVENT:
5039 		// Common to DW1 / DW2 / Noir
5040 		if (TinselVersion == TINSEL_V2 || TinselVersion == TINSEL_V3)
5041 			pp[0] = pic->event;
5042 		else
5043 			pp[0] = TINSEL1_EVENT_MAP[pic->event];
5044 		return 0;
5045 
5046 	case FACETAG:
5047 		// DW2 only
5048 		FaceTag(pp[0], pic->hPoly);
5049 		return -1;
5050 
5051 	case FADEIN:
5052 		// DW2 only
5053 		FadeInMedium();
5054 		return 0;
5055 
5056 	case FADEMIDI:
5057 		// DW1 only
5058 		FadeMidi(coroParam, pp[0]);
5059 		return -1;
5060 
5061 	case FADEMUSIC:
5062 		// Noir only
5063 		pp -= 1;
5064 		FadeMusic(pp[0], pp[1]);
5065 		return -2;
5066 
5067 	case FADEOUT:
5068 		// DW1 only
5069 		FadeOutMedium();
5070 		return 0;
5071 
5072 	case FRAMEGRAB:
5073 		// Common to both DW1 & DW2
5074 		return -1;
5075 
5076 	case FREEZECURSOR:
5077 		// DW2 only
5078 		FreezeCursor(pp[0]);
5079 		return -1;
5080 
5081 	case GETINVLIMIT:
5082 		// Common to both DW1 & DW2
5083 		pp[0] = GetInvLimit(pp[0]);
5084 		return 0;
5085 
5086 	case GHOST:
5087 		// DW2 only
5088 		pp -= 2;			// 3 parameters
5089 		Ghost(pp[0], pp[1], pp[2]);
5090 		return -3;
5091 
5092 	case GLOBALVAR:
5093 		// DW1 only
5094 		error("GlobalVar isn't a real function");
5095 
5096 	case GRABMOVIE:
5097 		// DW2 only
5098 		return -1;
5099 
5100 	case HAILSCENE:
5101 		// DW2 only
5102 		HailScene(pp[0]);
5103 		return -1;
5104 
5105 	case HASRESTARTED:
5106 		// Common to both DW1 & DW2
5107 		pp[0] = HasRestarted();
5108 		return 0;
5109 
5110 	case HAVE:
5111 		// DW2 / Noir
5112 		pp[0] = Have(pp[0]);
5113 		return 0;			// using return value
5114 
5115 	case HELDOBJECT:
5116 		// Common to both DW1 & DW2
5117 		pp[0] = HeldObject();
5118 		return 0;
5119 
5120 	case HIDEACTOR:
5121 		// Common to DW1 / DW2 / Noir
5122 		if (!TinselV2)
5123 			HideActorFn(coroParam, pp[0]);
5124 		else if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
5125 			*pResumeState = RES_NOT;
5126 			FinishWaiting(coroParam, pic);
5127 			if (coroParam) {
5128 				*pResumeState = RES_1;
5129 				return 0;
5130 			}
5131 		} else
5132 			HideActorFn(coroParam, pp[0]);
5133 		return -1;
5134 
5135 	case HIDEBLOCK:
5136 		// DW2 only
5137 		HideBlock(pp[0]);
5138 		return -1;
5139 
5140 	case HIDEEFFECT:
5141 		// DW2 only
5142 		HideEffect(pp[0]);
5143 		return -1;
5144 
5145 	case HIDEPATH:
5146 		// DW2 only
5147 		HidePath(pp[0]);
5148 		return -1;
5149 
5150 	case HIDEREFER:
5151 		// DW2 / Noir
5152 		HideRefer(pp[0]);
5153 		return -1;
5154 
5155 	case HIDETAG:
5156 		// DW2 only
5157 		if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
5158 			*pResumeState = RES_NOT;
5159 			FinishWaiting(coroParam, pic);
5160 			if (coroParam) {
5161 				*pResumeState = RES_1;
5162 				return 0;
5163 			}
5164 		} else {
5165 			HideTag(coroParam, pp[0], pic->hPoly);
5166 			if (coroParam)
5167 				return 0;
5168 		}
5169 		return -1;
5170 
5171 	case HOLD:
5172 		// DW2 only
5173 		Hold(pp[0]);
5174 		return -1;
5175 
5176 	case HOOKSCENE:
5177 		// Common to both DW1 & DW2
5178 		pp -= 2;			// 3 parameters
5179 		HookScene(pp[0], pp[1], pp[2]);
5180 		return -3;
5181 
5182 	case IDLETIME:
5183 		// Common to both DW1 & DW2
5184 		pp[0] = IdleTime();
5185 		return 0;
5186 
5187 	case ININVENTORY:
5188 		// DW1 only
5189 		pp[0] = InInventory(pp[0]);
5190 		return 0;			// using return value
5191 
5192 	case INSTANTSCROLL:
5193 		// DW2 only
5194 		InstantScroll(pp[0]);
5195 		return -1;
5196 
5197 	case INVDEPICT:
5198 		// DW1 only
5199 		pp -= 1;			// 2 parameters
5200 		InvDepict(pp[0], pp[1]);
5201 		return -2;
5202 
5203 	case INVENTORY:
5204 		// Common to both DW1 & DW2
5205 		Inventory(pp[0], pic->escOn, pic->myEscape);
5206 		return -1;
5207 
5208 	case INVPLAY:
5209 		// DW2 only
5210 		pp -= 1;			// 2 parameters
5211 		InvPlay(pp[0], pp[1]);
5212 		return -2;
5213 
5214 	case INWHICHINV:
5215 		// Common to both DW1 & DW2
5216 		pp[0] = InWhichInv(pp[0]);
5217 		return 0;			// using return value
5218 
5219 	case KILLACTOR:
5220 		// DW1 only
5221 		if (TinselV2)
5222 			error("KillActor() was not expected to be required");
5223 
5224 		KillActor(pp[0]);
5225 		return -1;
5226 
5227 	case KILLBLOCK:
5228 		// DW1 only
5229 		KillBlock(pp[0]);
5230 		return -1;
5231 
5232 	case KILLEXIT:
5233 		// DW1 only
5234 		KillExit(pp[0]);
5235 		return -1;
5236 
5237 	case KILLGLOBALPROCESS:
5238 		// DW2 only
5239 		KillGlobalProcess(pp[0]);
5240 		return -1;
5241 
5242 	case KILLPROCESS:
5243 		// DW2 / Noir
5244 		KillProcess(pp[0]);
5245 		return -1;
5246 
5247 	case KILLTAG:
5248 		// DW1 only
5249 		KillTag(coroParam, pp[0]);
5250 		return -1;
5251 
5252 	case LOCALVAR:
5253 		// DW2 only
5254 		error("LocalVar isn't a real function");
5255 
5256 	case MOVECURSOR:
5257 		// Common to DW1 / DW2 / Noir
5258 		pp -= 1;			// 2 parameters
5259 		MoveCursor(pp[0], pp[1]);
5260 		return -2;
5261 
5262 	case MOVETAG:
5263 		// DW2 only
5264 		pp -= 2;			// 3 parameters
5265 		MoveTag(pp[0], pp[1], pp[2], pic->hPoly);
5266 		return -3;
5267 
5268 	case MOVETAGTO:
5269 		// DW2 only
5270 		pp -= 2;			// 3 parameters
5271 		MoveTagTo(pp[0], pp[1], pp[2], pic->hPoly);
5272 		return -3;
5273 
5274 	case NEWSCENE:
5275 		// Common to DW1 / DW2 / Noir
5276 		pp -= 2;			// 3 parameters
5277 		if (*pResumeState == RES_2)
5278 			*pResumeState = RES_NOT;
5279 		else
5280 			NewScene(coroParam, pp[0], pp[1], pp[2]);
5281 		return -3;
5282 
5283 	case NOBLOCKING:
5284 		// Common to both DW1 & DW2
5285 		NoBlocking();
5286 		return 0;
5287 
5288 	case NOPAUSE:
5289 		// DW2 only
5290 		g_bNoPause = true;
5291 		return 0;
5292 
5293 	case NOSCROLL:
5294 		// Common to both DW1 & DW2
5295 		pp -= 3;			// 4 parameters
5296 		NoScroll(pp[0], pp[1], pp[2], pp[3]);
5297 		return -4;
5298 
5299 	case OBJECTHELD:
5300 		// DW1 only
5301 		ObjectHeld(pp[0]);
5302 		return -1;
5303 
5304 	case OFFSET:
5305 		// Common to both DW1 & DW2
5306 		if (TinselV2) {
5307 			pp -= 2;			// 2 parameters
5308 			Offset((EXTREME)pp[0], pp[1], pp[2]);
5309 			return -3;
5310 		} else {
5311 			pp -= 1;			// 2 parameters
5312 			Offset(EX_USEXY, pp[0], pp[1]);
5313 			return -2;
5314 		}
5315 
5316 	case OTHEROBJECT:
5317 		// DW2 only
5318 		pp[0] = OtherObject(pic->pinvo);
5319 		return 0;
5320 
5321 	case PAUSE:
5322 		// DW2 only
5323 		WaitTime(coroParam, 1, true, pic->escOn, pic->myEscape);
5324 		return 0;
5325 
5326 	case PLAY:
5327 		// Common to DW1 / DW2 / Noir
5328 		if (TinselV3) {
5329 			if (*pResumeState == RES_1 && _vm->_handle->IsCdPlayHandle(pp[0])) {
5330 				*pResumeState = RES_NOT;
5331 				if ((pp[0] & 0x10) != 0) {
5332 					return -4;
5333 				}
5334 				return -2;
5335 			} else if ((pp[0] & 0x10) != 0) {
5336 				Play(coroParam, pp[-1], pp[-3], pp[-2], pp[0], pic->myEscape, false, pic->event, pic->hPoly, pic->idActor);
5337 				return -4;
5338 			}
5339 			Play(coroParam, pp[-1], -1, -1, pp[0], pic->myEscape, false, pic->event, pic->hPoly, pic->idActor);
5340 			return -2;
5341 
5342 		} if (TinselV2) {
5343 			pp -= 3;			// 4 parameters
5344 			if (*pResumeState == RES_1 && _vm->_handle->IsCdPlayHandle(pp[0]))
5345 				*pResumeState = RES_NOT;
5346 			else {
5347 				Play(coroParam, pp[0], pp[1], pp[2], pp[3], pic->myEscape, false, pic->event, pic->hPoly, pic->idActor);
5348 			}
5349 			return -4;
5350 
5351 		} else {
5352 			pp -= 5;			// 6 parameters
5353 
5354 			if (pic->event == WALKIN || pic->event == WALKOUT)
5355 				Play(coroParam, pp[0], pp[1], pp[2], pp[5], 0, false, 0, pic->escOn, pic->myEscape, false);
5356 			else
5357 				Play(coroParam, pp[0], pp[1], pp[2], pp[5], pic->idActor, false, 0, pic->escOn, pic->myEscape, false);
5358 			return -6;
5359 		}
5360 
5361 	case PLAYMIDI:
5362 		// Common to both DW1 & DW2
5363 		pp -= 2;			// 3 parameters
5364 		PlayMidi(coroParam, pp[0], pp[1], pp[2]);
5365 		return -3;
5366 
5367 	case PLAYMOVIE:
5368 		if (TinselV3) {
5369 			t3PlayMovie(coroParam, pp[0], pic->myEscape);
5370 		} else {
5371 			// DW2 only
5372 			PlayMovie(coroParam, pp[0], pic->myEscape);
5373 		}
5374 		return -1;
5375 
5376 	case PLAYMUSIC:
5377 		// DW2 / Noir only
5378 		PlayMusic(pp[0]);
5379 		return -1;
5380 
5381 	case PLAYRTF:
5382 		// Common to both DW1 & DW2
5383 		error("playrtf only applies to cdi");
5384 
5385 	case PLAYSAMPLE:
5386 		// Common to DW1 / DW2 / Noir
5387 		if (TinselV2 || TinselV3) {
5388 			pp -= 3;			// 4 parameters
5389 			PlaySample(coroParam, pp[0], pp[1], pp[2], pp[3], pic->myEscape);
5390 			return -4;
5391 		} else {
5392 			pp -= 1;			// 2 parameters
5393 			PlaySample(coroParam, pp[0], pp[1], pic->escOn, pic->myEscape);
5394 			return -2;
5395 		}
5396 
5397 	case POINTACTOR:
5398 		// DW2 only
5399 		PointActor(pp[0]);
5400 		return -1;
5401 
5402 	case POINTTAG:
5403 		// DW2 only
5404 		PointTag(pp[0], pic->hPoly);
5405 		return -1;
5406 
5407 	case POSTACTOR:
5408 		// DW2 only
5409 		pp -= 1;			// 2 parameters
5410 		PostActor(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->hPoly, pic->idActor, pic->myEscape);
5411 		return -2;
5412 
5413 	case POSTGLOBALPROCESS:
5414 		// DW2 only
5415 		pp -= 1;			// 2 parameters
5416 		PostGlobalProcess(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape);
5417 		return -2;
5418 
5419 	case POSTOBJECT:
5420 		// DW2 only
5421 		pp -= 1;			// 2 parameters
5422 		PostObject(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape);
5423 		return -2;
5424 
5425 	case POSTPROCESS:
5426 		// DW2 only
5427 		pp -= 1;			// 2 parameters
5428 		PostProcess(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->myEscape);
5429 		return -2;
5430 
5431 	case POSTTAG:
5432 		// DW2 / Noir
5433 		pp -= 1;			// 2 parameters
5434 		PostTag(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->hPoly, pic->myEscape);
5435 		return -2;
5436 
5437 
5438 	case PREPARESCENE:
5439 		// DW1 only
5440 		PrepareScene(pp[0]);
5441 		return -1;
5442 
5443 	case PRINT:
5444 		// Common to both DW1 & DW2
5445 		if (TinselV2) {
5446 			pp -= 4;			// 5 parameters
5447 			Print(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4] != 0, pic->escOn, pic->myEscape);
5448 			return -5;
5449 		} else {
5450 			pp -= 5;			// 6 parameters
5451 			/* pp[2] was intended to be attribute */
5452 			Print(coroParam, pp[0], pp[1], pp[3], pp[4], pp[5] == 2, pic->escOn, pic->myEscape);
5453 			return -6;
5454 		}
5455 
5456 	case PRINTCURSOR:
5457 		// DW2 / Noir only
5458 		PrintTag(pic->hPoly, pp[0], pic->idActor, true);
5459 		return -1;
5460 
5461 	case PRINTOBJ:
5462 		// Common to both DW1 & DW2
5463 		PrintObj(coroParam, pp[0], pic->pinvo, pic->event, pic->myEscape);
5464 		return -1;
5465 
5466 	case PRINTTAG:
5467 		// Common to DW1 / DW2 / Noir
5468 		PrintTag(pic->hPoly, pp[0], TinselV2 ? pic->idActor : 0, false);
5469 		return -1;
5470 
5471 	case QUITGAME:
5472 		// Common to both DW1 & DW2
5473 		QuitGame();
5474 		return 0;
5475 
5476 	case RANDOM:
5477 		// Common to DW1 / DW2 / Noir
5478 		pp -= 2;			// 3 parameters
5479 		pp[0] = RandomFn(pp[0], pp[1], pp[2]);
5480 		return -2;		// One holds return value
5481 
5482 	case RESETIDLETIME:
5483 		// Common to both DW1 & DW2
5484 		ResetIdleTime();
5485 		return 0;
5486 
5487 	case RESTARTGAME:
5488 		// Common to both DW1 & DW2
5489 		FnRestartGame();
5490 		return 0;
5491 
5492 	case RESTORESCENE:
5493 		// Common to both DW1 & DW2
5494 		if (TinselV2) {
5495 			RestoreScene(coroParam, (TRANSITS)pp[0]);
5496 			return -1;
5497 		} else {
5498 			RestoreScene(coroParam, TRANS_FADE);
5499 			return 0;
5500 		}
5501 
5502 	case RESTORE_CUT:
5503 		// DW1 only
5504 		RestoreScene(coroParam, TRANS_CUT);
5505 		return 0;
5506 
5507 	case RESUMELASTGAME:
5508 		// DW2 only
5509 		ResumeLastGame();
5510 		return 0;
5511 
5512 	case RUNMODE:
5513 		// Common to both DW1 & DW2
5514 		pp[0] = RunMode();
5515 		return 0;
5516 
5517 	case SAMPLEPLAYING:
5518 		// DW1 only
5519 		pp[0] = SamplePlaying(pic->escOn, pic->myEscape);
5520 		return 0;
5521 
5522 	case SAVESCENE:
5523 		// Common to both DW1 & DW2
5524 		if (*pResumeState == RES_1)
5525 			*pResumeState = RES_2;
5526 		else
5527 			SaveScene(coroParam);
5528 		return 0;
5529 
5530 	case SAY:
5531 		// DW2 only
5532 		pp -= 1;			// 2 parameters
5533 		TalkOrSay(coroParam, IS_SAY, pp[1], 0, 0, 0, pp[0], false, pic->escOn, pic->myEscape);
5534 		return -2;
5535 
5536 	case SAYAT:
5537 		// DW2 only
5538 		pp -= 4;			// 5 parameters
5539 		TalkOrSay(coroParam, IS_SAYAT, pp[3], pp[1], pp[2], 0, pp[0], pp[4], pic->escOn, pic->myEscape);
5540 		return -5;
5541 
5542 	case VOICEOVER:
5543 		// Noir only
5544 		TalkOrSay(coroParam, IS_SAY, pp[0], 0, 0, 0, SystemVar(SV_USER2), false, pic->escOn, pic->myEscape);
5545 		return -1;
5546 
5547 	case SCALINGREELS:
5548 		// Common to both DW1 & DW2
5549 		pp -= 6;			// 7 parameters
5550 		ScalingReels(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]);
5551 		return -7;
5552 
5553 	case SCANICON:
5554 		// DW1 only
5555 		pp[0] = ScanIcon();
5556 		return 0;
5557 
5558 	case SCREENXPOS:
5559 		// Common to both DW1 & DW2
5560 		pp[0] = LToffset(SCREENXPOS);
5561 		return 0;
5562 
5563 	case SCREENYPOS:
5564 		// Common to both DW1 & DW2
5565 		pp[0] = LToffset(SCREENYPOS);
5566 		return 0;
5567 
5568 	case SCROLL:
5569 		// Common to both DW1 & DW2
5570 		if (TinselV2) {
5571 			pp -= 5;			// 6 parameters
5572 			ScrollScreen(coroParam, (EXTREME)pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pic->escOn, pic->myEscape);
5573 			return -6;
5574 		} else {
5575 			pp -= 3;			// 4 parameters
5576 			ScrollScreen(coroParam, EX_USEXY, pp[0], pp[1], pp[2], pp[2], pp[3], pic->escOn, pic->myEscape);
5577 			return -4;
5578 		}
5579 
5580 	case SCROLLPARAMETERS:
5581 		// DW2 only
5582 		pp -= 6;			// 7 parameters
5583 		ScrollParameters(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]);
5584 		return -7;
5585 
5586 	case SENDTAG:
5587 		// DW2 only
5588 		pp -= 1;			// 2 parameters
5589 		if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
5590 			bool result;
5591 			*pResumeState = RES_NOT;
5592 			FinishWaiting(coroParam, pic, &result);
5593 			if (coroParam) {
5594 				*pResumeState = RES_1;
5595 				return 0;
5596 			}
5597 			pp[0] = result ? 1 : 0;
5598 		} else {
5599 			bool result;
5600 			SendTag(coroParam, pp[0], (TINSEL_EVENT)pp[1], pic->hPoly, pic->myEscape, &result);
5601 			if (coroParam)
5602 				return 0;
5603 
5604 			pp[0] = result;
5605 		}
5606 		return -1;
5607 
5608 	case SET3DTEXTURE:
5609 		// Noir only
5610 		warning("TODO: Implement SET3DTEXTURE(0x%08X)", pp[0]);
5611 		return -1;
5612 
5613 	case SETACTOR:
5614 		// DW1 only
5615 		SetActor(pp[0]);
5616 		return -1;
5617 
5618 	case SETBLOCK:
5619 		// DW1 only
5620 		SetBlock(pp[0]);
5621 		return -1;
5622 
5623 	case SETEXIT:
5624 		// DW1 only
5625 		SetExit(pp[0]);
5626 		return -1;
5627 
5628 	case SETINVLIMIT:
5629 		// Common to both DW1 & DW2
5630 		pp -= 1;			// 2 parameters
5631 		SetInvLimit(pp[0], pp[1]);
5632 		return -2;
5633 
5634 	case SETINVSIZE:
5635 		// Common to both DW1 & DW2
5636 		pp -= 6;			// 7 parameters
5637 		SetInvSize(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]);
5638 		return -7;
5639 
5640 	case SETLANGUAGE:
5641 		// Common to both DW1 & DW2
5642 		SetLanguage((LANGUAGE)pp[0]);
5643 		return -1;
5644 
5645 	case SETPALETTE:
5646 		// Common to both DW1 & DW2
5647 		if (TinselV2) {
5648 			// Note: Although DW2 introduces parameters for start and length, it doesn't use them
5649 			pp -= 2;
5650 			SetPalette(pp[0], pic->escOn, pic->myEscape);
5651 			return -3;
5652 		} else {
5653 			SetPalette(pp[0], pic->escOn, pic->myEscape);
5654 			return -1;
5655 		}
5656 
5657 	case SETSYSTEMREEL:
5658 		// Noir only
5659 		if (TinselV3) {
5660 			pp -= 1;
5661 			SetSystemReel(pp[0], pp[1]);
5662 			return -2;
5663 		} else {
5664 			error("SETSYSTEMREEL is only used in Noir");
5665 		}
5666 
5667 	case SETSYSTEMSTRING:
5668 		// DW2 & Noir
5669 		pp -= 1;				// 2 parameters
5670 		SetSystemString(pp[0], pp[1]);
5671 		return -2;
5672 
5673 	case SETSYSTEMVAR:
5674 		// DW1 & Noir
5675 		pp -= 1;				// 2 parameters
5676 		SetSystemVar(pp[0], pp[1]);
5677 		return -2;
5678 
5679 	case SETTAG:
5680 		// DW1 only
5681 		SetTag(coroParam, pp[0]);
5682 		return -1;
5683 
5684 	case SETTIMER:
5685 		// DW1 only
5686 		pp -= 3;			// 4 parameters
5687 		SetTimer(pp[0], pp[1], pp[2], pp[3]);
5688 		return -4;
5689 
5690 	case SETVIEW:
5691 		// Noir only
5692 		pp -= 1;
5693 		warning("TODO: Implement SETVIEW(0x%08X, %i)", pp[0], pp[1]);
5694 		return -2;
5695 
5696 	case SHELL:
5697 		// DW2 only
5698 		Shell(pp[0]);
5699 		return 0;
5700 
5701 	case SHOWACTOR:
5702 		// DW2 & Noir
5703 		if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
5704 			*pResumeState = RES_NOT;
5705 			FinishWaiting(coroParam, pic);
5706 			if (coroParam) {
5707 				*pResumeState = RES_1;
5708 				return 0;
5709 			}
5710 		} else
5711 			ShowActorFn(coroParam, pp[0]);
5712 		return -1;
5713 
5714 	case SHOWBLOCK:
5715 		// DW2 only
5716 		ShowBlock(pp[0]);
5717 		return -1;
5718 
5719 	case SHOWEFFECT:
5720 		// DW2 only
5721 		ShowEffect(pp[0]);
5722 		return -1;
5723 
5724 	case SHOWMENU:
5725 		// DW2 / Noir
5726 		ShowMenu();
5727 		return 0;
5728 
5729 	case SHOWPATH:
5730 		// DW2 only
5731 		ShowPath(pp[0]);
5732 		return -1;
5733 
5734 	case SHOWPOS:
5735 		// DW1 only
5736 #ifdef DEBUG
5737 		showpos();
5738 #endif
5739 		return 0;
5740 
5741 	case SHOWREFER:
5742 		// DW2 only
5743 		ShowRefer(pp[0]);
5744 		return -1;
5745 
5746 	case SHOWSTRING:
5747 #ifdef DEBUG
5748 		showstring();
5749 #endif
5750 		return 0;
5751 
5752 	case SHOWTAG:
5753 		// DW2 / Noir
5754 		if (*pResumeState == RES_1 && pic->resumeCode == RES_WAITING) {
5755 			*pResumeState = RES_NOT;
5756 			FinishWaiting(coroParam, pic);
5757 
5758 			if (coroParam) {
5759 				*pResumeState = RES_1;
5760 				return 0;
5761 			}
5762 		} else {
5763 			ShowTag(coroParam, pp[0], pic->hPoly);
5764 		}
5765 		return -1;
5766 
5767 	case SPLAY:
5768 		// DW1 only
5769 		pp -= 6;			// 7 parameters
5770 
5771 		if (pic->event == WALKIN || pic->event == WALKOUT)
5772 			SPlay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], 0, pic->escOn, pic->myEscape);
5773 		else
5774 			SPlay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], pic->idActor, pic->escOn, pic->myEscape);
5775 		return -7;
5776 
5777 	case STAND:
5778 		// Common to DW1 / DW2 / Noir
5779 		pp -= 3;			// 4 parameters
5780 		Stand(coroParam, pp[0], pp[1], pp[2], pp[3]);
5781 		return -4;
5782 
5783 	case STANDTAG:
5784 		// Common to both DW1 & DW2
5785 		StandTag(pp[0], pic->hPoly);
5786 		return -1;
5787 
5788 	case STARTGLOBALPROCESS:
5789 		// DW2 only
5790 		StartGlobalProcess(coroParam, pp[0]);
5791 		return -1;
5792 
5793 	case STARTPROCESS:
5794 		// DW2 / Noir
5795 		StartProcess(coroParam, pp[0]);
5796 		return -1;
5797 
5798 	case STARTTIMER:
5799 		// DW2 only
5800 		pp -= 3;			// 4 parameters
5801 		StartTimerFn(pp[0], pp[1], pp[2], pp[3]);
5802 		return -4;
5803 
5804 	case STOPMIDI:
5805 		// DW1 only
5806 		StopMidiFn();
5807 		return 0;
5808 
5809 	case STOPSAMPLE:
5810 		// Common to both DW1 & DW2
5811 		if (TinselV2) {
5812 			StopSample(pp[0]);
5813 			return -1;
5814 		} else {
5815 			StopSample();
5816 			return 0;
5817 		}
5818 
5819 	case STOPWALK:
5820 		// Common to both DW1 & DW2 only
5821 		StopWalk(pp[0]);
5822 		return -1;
5823 
5824 	case SUBTITLES:
5825 		// Common to both DW1 & DW2
5826 		Subtitles(pp[0]);
5827 		return -1;
5828 
5829 	case SWALK:
5830 		// Common to both DW1 & DW2
5831 		pp -= 5;			// 6 parameters
5832 		Swalk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], -1, pic->escOn, pic->myEscape);
5833 		return -6;
5834 
5835 	case SWALKZ:
5836 		// DW2 only
5837 		pp -= 6;			// 7 parameters
5838 		Swalk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6], pic->escOn, pic->myEscape);
5839 		return -7;
5840 
5841 	case SYSTEMVAR:
5842 		// DW2 / Noir
5843 		pp[0] = SystemVar(pp[0]);
5844 		return 0;
5845 
5846 	case TAGACTOR:
5847 		pp -= 2;			// 3 parameters
5848 		TagActor(pp[0], pp[1], pp[2]);
5849 		return -3;
5850 
5851 	case TAGTAGXPOS:
5852 	case TAGTAGYPOS:
5853 	case TAGWALKXPOS:
5854 	case TAGWALKYPOS:
5855 		// DW2 only
5856 		pp[0] = TagPos((MASTER_LIB_CODES)libCode, pp[0], pic->hPoly);
5857 		return 0;
5858 
5859 	case TALK:
5860 		// Common to both DW1 & DW2
5861 		pp -= 1;			// 2 parameters
5862 
5863 		if (TinselV2)
5864 			TalkOrSay(coroParam, IS_TALK, pp[1], 0, 0, pp[0], 0, false, pic->escOn, pic->myEscape);
5865 		else if (pic->event == WALKIN || pic->event == WALKOUT)
5866 			TalkOrSay(coroParam, IS_TALK, pp[1], 0, 0, pp[0], 0, false, pic->escOn, pic->myEscape);
5867 		else
5868 			TalkOrSay(coroParam, IS_TALK, pp[1], 0, 0, pp[0], pic->idActor, false, pic->escOn, pic->myEscape);
5869 		return -2;
5870 
5871 	case TALKAT:
5872 		// Common to both DW1 & DW2
5873 		if (TinselV2) {
5874 			pp -= 4;			// 5 parameters
5875 			TalkOrSay(coroParam, IS_TALKAT, pp[3], pp[1], pp[2], 0, pp[0], pp[4], pic->escOn, pic->myEscape);
5876 			return -5;
5877 		} else {
5878 			pp -= 3;			// 4 parameters
5879 			TalkAt(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myEscape);
5880 			return -4;
5881 		}
5882 
5883 	case TALKATS:
5884 		// DW1 only
5885 		pp -= 4;			// 5 parameters
5886 		TalkAtS(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pic->escOn, pic->myEscape);
5887 		return -5;
5888 
5889 	case TALKATTR:
5890 		// DW1 only
5891 		pp -= 2;			// 3 parameters
5892 		TalkAttr(pp[0], pp[1], pp[2], pic->escOn, pic->myEscape);
5893 		return -3;
5894 
5895 	case TALKPALETTEINDEX:
5896 		// DW1 only
5897 		TalkPaletteIndex(pp[0]);
5898 		return -1;
5899 
5900 	case TALKRGB:
5901 		// DW2 only
5902 		TalkRGB(pp[0], pic->myEscape);
5903 		return -3;
5904 
5905 	case TALKVIA:
5906 		// DW2 / Noir
5907 		TalkVia(pp[0]);
5908 		return -1;
5909 
5910 	case TEMPTAGFONT:
5911 		// DW2 only
5912 		_vm->_font->SetTempTagFontHandle(pp[0]);
5913 		return -1;
5914 
5915 	case TEMPTALKFONT:
5916 		// DW2 only
5917 		_vm->_font->SetTempTalkFontHandle(pp[0]);
5918 		return -1;
5919 
5920 	case THISOBJECT:
5921 		// DW2 only
5922 		pp[0] = ThisObject(pic->pinvo);
5923 		return 0;
5924 
5925 	case THISTAG:
5926 		// DW2 only
5927 		pp[0] = ThisTag(pic->hPoly);
5928 		return 0;
5929 
5930 	case TIMER:
5931 		// Common to both DW1 & DW2
5932 		pp[0] = TimerFn(pp[0]);
5933 		return 0;
5934 
5935 	case TOPIC:
5936 		// DW2 only
5937 		pp[0] = Topic();
5938 		return 0;
5939 
5940 	case TOPPLAY:
5941 		// Common to both DW1 & DW2
5942 		if (TinselV2) {
5943 			pp -= 3;			// 4 parameters
5944 			TopPlay(coroParam, pp[0], pp[1], pp[2], pp[3], pic->myEscape, pic->event);
5945 			return -4;
5946 		} else {
5947 			pp -= 5;			// 6 parameters
5948 			TopPlay(coroParam, pp[0], pp[1], pp[2], pp[5], pic->idActor, false, 0, pic->escOn, pic->myEscape);
5949 			return -6;
5950 		}
5951 
5952 	case TOPWINDOW:
5953 		// Common to both DW1 & DW2
5954 		TopWindow(pp[0]);
5955 		return -1;
5956 
5957 	case TRANSLUCENTINDEX:
5958 		// DW2 only
5959 		TranslucentIndex(pp[0]);
5960 		return -1;
5961 
5962 	case TRYPLAYSAMPLE:
5963 		// DW1 only
5964 		pp -= 1;			// 2 parameters
5965 		TryPlaySample(coroParam, pp[0], pp[1], pic->escOn, pic->myEscape);
5966 		return -2;
5967 
5968 	case UNDIMMUSIC:
5969 		// DW2 only
5970 		_vm->_pcmMusic->unDim(true);
5971 		return 0;
5972 
5973 	case UNHOOKSCENE:
5974 		// Common to both DW1 & DW2
5975 		UnHookScene();
5976 		return 0;
5977 
5978 	case UNTAGACTOR:
5979 		// DW1 only
5980 		UnTagActorFn(pp[0]);
5981 		return -1;
5982 
5983 	case VIBRATE:
5984 		// DW1 only
5985 		Vibrate();
5986 		return 0;
5987 
5988 	case WAITFRAME:
5989 		// Common to both DW1 & DW2
5990 		pp -= 1;			// 2 parameters
5991 		WaitFrame(coroParam, pp[0], pp[1], pic->escOn, pic->myEscape);
5992 		return -2;
5993 
5994 	case WAITKEY:
5995 		// Common to both DW1 & DW2
5996 		WaitKey(coroParam, pic->escOn, pic->myEscape);
5997 		return 0;
5998 
5999 	case WAITSCROLL:
6000 		// DW2 only
6001 		WaitScroll(coroParam, pic->myEscape);
6002 		return 0;
6003 
6004 	case WAITTIME:
6005 		// Common to DW1 / DW2 / Noir
6006 		pp -= 1;			// 2 parameters
6007 		WaitTime(coroParam, pp[0], pp[1], pic->escOn, pic->myEscape);
6008 		if (!coroParam && (pic->hCode == 0x3007540) && (pic->resumeState == RES_2))
6009 			// FIXME: This is a hack to return control to the user after using the prunes in
6010 			// the DW1 Demo, since I can't figure out how it's legitimately done
6011 			Control(CONTROL_ON);
6012 
6013 		return -2;
6014 
6015 	case WALK:
6016 		// Common to both DW1 & DW2
6017 		pp -= 4;			// 5 parameters
6018 		Walk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], false, -1, pic->escOn, pic->myEscape);
6019 		return -5;
6020 
6021 	case WALKED: {
6022 		// Common to both DW1 & DW2
6023 		pp -= 3;			// 4 parameters
6024 		bool tmp = false;
6025 		Walked(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myEscape, tmp);
6026 		if (!coroParam) {
6027 			// Only write the result to the stack if walked actually completed running.
6028 			pp[0] = tmp;
6029 		}
6030 		}
6031 		return -3;
6032 
6033 	case WALKINGACTOR:
6034 		// Common to both DW1 & DW2
6035 		if (TinselV2) {
6036 			// DW2 doesn't use a second parameter to WalkingActor
6037 			WalkingActor(pp[0]);
6038 			return -1;
6039 		} else {
6040 			pp -= 40;			// 41 parameters
6041 			WalkingActor(pp[0], (SCNHANDLE *)&pp[1]);
6042 			return -41;
6043 		}
6044 
6045 	case WALKPOLY:
6046 		// Common to both DW1 & DW2
6047 		if (TinselV2) {
6048 			pp -= 1;			// 2 parameters
6049 			WalkPoly(coroParam, pp[0], pp[1], pic->hPoly, pic->escOn, pic->myEscape);
6050 			return -2;
6051 		} else {
6052 			pp -= 2;			// 3 parameters
6053 			WalkPoly(coroParam, pp[0], pp[1], pic->hPoly, pic->escOn, pic->myEscape);
6054 			return -3;
6055 		}
6056 
6057 	case WALKTAG:
6058 		// Common to both DW1 & DW2
6059 		if (TinselV2) {
6060 			pp -= 1;			// 2 parameters
6061 			WalkTag(coroParam, pp[0], pp[1], pic->hPoly, pic->escOn, pic->myEscape);
6062 			return -2;
6063 		} else {
6064 			pp -= 2;			// 3 parameters
6065 			WalkTag(coroParam, pp[0], pp[1], pic->hPoly, pic->escOn, pic->myEscape);
6066 			return -3;
6067 		}
6068 
6069 	case WALKXPOS:
6070 		// DW2 only
6071 		pp[0] = WalkXPos();
6072 		return 0;
6073 
6074 	case WALKYPOS:
6075 		// DW2 only
6076 		pp[0] = WalkYPos();
6077 		return 0;
6078 
6079 	case WHICHCD:
6080 		// DW2 / Noir
6081 		pp[0] = WhichCd();
6082 		return 0;
6083 
6084 	case WHICHINVENTORY:
6085 		// Common to DW1 / DW2 / Noir
6086 		pp[0] = WhichInventory();
6087 		return 0;
6088 
6089 	case ZZZZZZ:
6090 		// DW2 only - dummy routine used during debugging
6091 		return -1;
6092 
6093 	default:
6094 		error("Unsupported library function");
6095 	}
6096 
6097 	//error("Can't possibly get here");
6098 }
6099 
6100 } // End of namespace Tinsel
6101