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  */
22 
23 #include "common/debug-channels.h"
24 #include "common/endian.h"
25 #include "common/error.h"
26 #include "common/events.h"
27 #include "common/keyboard.h"
28 #include "common/fs.h"
29 #include "common/config-manager.h"
30 #include "common/serializer.h"
31 
32 #include "backends/audiocd/audiocd.h"
33 
34 #include "engines/util.h"
35 
36 #include "tinsel/actors.h"
37 #include "tinsel/background.h"
38 #include "tinsel/bmv.h"
39 #include "tinsel/config.h"
40 #include "tinsel/cursor.h"
41 #include "tinsel/drives.h"
42 #include "tinsel/dw.h"
43 #include "tinsel/events.h"
44 #include "tinsel/faders.h"
45 #include "tinsel/film.h"
46 #include "tinsel/handle.h"
47 #include "tinsel/heapmem.h"			// MemoryInit
48 #include "tinsel/dialogs.h"
49 #include "tinsel/mareels.h"
50 #include "tinsel/music.h"
51 #include "tinsel/object.h"
52 #include "tinsel/pid.h"
53 #include "tinsel/polygons.h"
54 #include "tinsel/savescn.h"
55 #include "tinsel/scn.h"
56 #include "tinsel/sound.h"
57 #include "tinsel/strres.h"
58 #include "tinsel/sysvar.h"
59 #include "tinsel/timers.h"
60 #include "tinsel/tinsel.h"
61 
62 namespace Tinsel {
63 
64 //----------------- EXTERNAL FUNCTIONS ---------------------
65 
66 // In BG.CPP
67 extern void SetDoFadeIn(bool tf);
68 extern void DropBackground();
69 extern const BACKGND *g_pCurBgnd;
70 
71 // In CURSOR.CPP
72 extern void CursorProcess(CORO_PARAM, const void *);
73 
74 // In INVENTORY.CPP
75 extern void InventoryProcess(CORO_PARAM, const void *);
76 
77 // In SCENE.CPP
78 extern void PrimeBackground();
79 extern SCNHANDLE GetSceneHandle();
80 
81 //----------------- FORWARD DECLARATIONS  ---------------------
82 void SetNewScene(SCNHANDLE scene, int entrance, int transition);
83 
84 //----------------- GLOBAL GLOBAL DATA --------------------
85 
86 // FIXME: Avoid non-const global vars
87 
88 bool g_bRestart = false;
89 bool g_bHasRestarted = false;
90 bool g_loadingFromGMM = false;
91 
92 static bool g_bCuttingScene = false;
93 
94 static bool g_bChangingForRestore = false;
95 
96 #ifdef DEBUG
97 bool g_bFast;		// set to make it go ludicrously fast
98 #endif
99 
100 //----------------- LOCAL GLOBAL DATA --------------------
101 
102 struct Scene {
103 	SCNHANDLE scene;	// Memory handle for scene
104 	int	entry;		// Entrance number
105 	int	trans;		// Transition - not yet used
106 };
107 
108 static Scene g_NextScene = { 0, 0, 0 };
109 static Scene g_HookScene = { 0, 0, 0 };
110 static Scene g_DelayedScene = { 0, 0, 0 };
111 
112 static Common::PROCESS *g_pMouseProcess = 0;
113 static Common::PROCESS *g_pKeyboardProcess = 0;
114 
115 static SCNHANDLE g_hCdChangeScene;
116 
117 //----------------- LOCAL PROCEDURES --------------------
118 
119 /**
120  * Process to handle keypresses
121  */
KeyboardProcess(CORO_PARAM,const void *)122 void KeyboardProcess(CORO_PARAM, const void *) {
123 	// COROUTINE
124 	CORO_BEGIN_CONTEXT;
125 	CORO_END_CONTEXT(_ctx);
126 
127 	CORO_BEGIN_CODE(_ctx);
128 	while (true) {
129 		if (_vm->_keypresses.empty()) {
130 			// allow scheduling
131 			CORO_SLEEP(1);
132 			continue;
133 		}
134 
135 		// Get the next keyboard event off the stack
136 		Common::Event evt = _vm->_keypresses.front();
137 		_vm->_keypresses.pop_front();
138 
139 		// Switch for special keys
140 		switch (evt.kbd.keycode) {
141 		// Drag action
142 		case Common::KEYCODE_LALT:
143 		case Common::KEYCODE_RALT:
144 			if (evt.type == Common::EVENT_KEYDOWN) {
145 				if (!_vm->_config->_swapButtons)
146 					ProcessButEvent(PLR_DRAG2_START);
147 				else
148 					ProcessButEvent(PLR_DRAG1_START);
149 			} else {
150 				if (!_vm->_config->_swapButtons)
151 					ProcessButEvent(PLR_DRAG1_END);
152 				else
153 					ProcessButEvent(PLR_DRAG2_END);
154 			}
155 			continue;
156 
157 		case Common::KEYCODE_LCTRL:
158 		case Common::KEYCODE_RCTRL:
159 			if (evt.type == Common::EVENT_KEYDOWN) {
160 				ProcessKeyEvent(PLR_LOOK);
161 			} else {
162 				// Control key release
163 			}
164 			continue;
165 
166 		default:
167 			break;
168 		}
169 
170 		// At this point only key down events need processing
171 		if (evt.type == Common::EVENT_KEYUP)
172 			continue;
173 
174 		if (_vm->_keyHandler != NULL)
175 			// Keyboard is hooked, so pass it on to that handler first
176 			if (!_vm->_keyHandler(evt.kbd))
177 				continue;
178 
179 		switch (evt.kbd.keycode) {
180 		/*** SPACE = WALKTO ***/
181 		case Common::KEYCODE_SPACE:
182 			ProcessKeyEvent(PLR_WALKTO);
183 			continue;
184 
185 		/*** RETURN = ACTION ***/
186 		case Common::KEYCODE_RETURN:
187 		case Common::KEYCODE_KP_ENTER:
188 			ProcessKeyEvent(PLR_ACTION);
189 			continue;
190 
191 		/*** l = LOOK ***/
192 		case Common::KEYCODE_l:		// LOOK
193 			ProcessKeyEvent(PLR_LOOK);
194 			continue;
195 
196 		case Common::KEYCODE_ESCAPE:
197 			ProcessKeyEvent(PLR_ESCAPE);
198 			continue;
199 
200 #ifdef SLOW_RINCE_DOWN
201 		case '>':
202 			AddInterlude(1);
203 			continue;
204 		case '<':
205 			AddInterlude(-1);
206 			continue;
207 #endif
208 
209 		case Common::KEYCODE_1:
210 		case Common::KEYCODE_F1:
211 			// Options dialog
212 			ProcessKeyEvent(PLR_MENU);
213 			continue;
214 		case Common::KEYCODE_5:
215 		case Common::KEYCODE_F5:
216 			// Save game
217 			ProcessKeyEvent(PLR_SAVE);
218 			continue;
219 		case Common::KEYCODE_7:
220 		case Common::KEYCODE_F7:
221 			// Load game
222 			ProcessKeyEvent(PLR_LOAD);
223 			continue;
224 		case Common::KEYCODE_m:
225 			// Debug facility - scene hopper
226 			if (TinselV2 && (evt.kbd.hasFlags(Common::KBD_ALT)))
227 				ProcessKeyEvent(PLR_JUMP);
228 			break;
229 		case Common::KEYCODE_q:
230 			if ((evt.kbd.hasFlags(Common::KBD_CTRL)) || (evt.kbd.hasFlags(Common::KBD_ALT)))
231 				ProcessKeyEvent(PLR_QUIT);
232 			continue;
233 		case Common::KEYCODE_PAGEUP:
234 		case Common::KEYCODE_KP9:
235 			ProcessKeyEvent(PLR_PGUP);
236 			continue;
237 		case Common::KEYCODE_PAGEDOWN:
238 		case Common::KEYCODE_KP3:
239 			ProcessKeyEvent(PLR_PGDN);
240 			continue;
241 		case Common::KEYCODE_HOME:
242 		case Common::KEYCODE_KP7:
243 			ProcessKeyEvent(PLR_HOME);
244 			continue;
245 		case Common::KEYCODE_END:
246 		case Common::KEYCODE_KP1:
247 			ProcessKeyEvent(PLR_END);
248 			continue;
249 		default:
250 			ProcessKeyEvent(PLR_NOEVENT);
251 			break;
252 		}
253 	}
254 	CORO_END_CODE;
255 }
256 
257 /**
258  * Handles launching a single click action result if the timeout for a double-click
259  * expires
260  */
SingleLeftProcess(CORO_PARAM,const void * param)261 static void SingleLeftProcess(CORO_PARAM, const void *param) {
262 	CORO_BEGIN_CONTEXT;
263 		uint32 endTicks;
264 	CORO_END_CONTEXT(_ctx);
265 
266 	CORO_BEGIN_CODE(_ctx);
267 
268 	// Work out when to wait until
269 	_ctx->endTicks = DwGetCurrentTime() + (uint32)_vm->_config->_dclickSpeed;
270 
271 	// Timeout a double click (may not work once every 49 days!)
272 	do {
273 		CORO_SLEEP(1);
274 	} while (DwGetCurrentTime() < _ctx->endTicks);
275 
276 	if (GetProvNotProcessed()) {
277 		const Common::Point clickPos = *(const Common::Point *)param;
278 		PlayerEvent(PLR_WALKTO, clickPos);
279 	}
280 
281 	CORO_KILL_SELF();
282 	CORO_END_CODE;
283 }
284 
285 /**
286  * Process to handle changes in the mouse buttons.
287  */
MouseProcess(CORO_PARAM,const void *)288 static void MouseProcess(CORO_PARAM, const void *) {
289 	// COROUTINE
290 	CORO_BEGIN_CONTEXT;
291 		bool lastLWasDouble;
292 		bool lastRWasDouble;
293 		uint32 lastLeftClick, lastRightClick;
294 		Common::Point clickPos;
295 	CORO_END_CONTEXT(_ctx);
296 
297 	CORO_BEGIN_CODE(_ctx);
298 
299 	_ctx->lastLWasDouble = false;
300 	_ctx->lastRWasDouble = false;
301 	_ctx->lastLeftClick = _ctx->lastRightClick = DwGetCurrentTime();
302 
303 	while (true) {
304 
305 		if (_vm->_mouseButtons.empty()) {
306 			// allow scheduling
307 			CORO_SLEEP(1);
308 			continue;
309 		}
310 
311 		// get next mouse button event
312 		Common::EventType type = _vm->_mouseButtons.front();
313 		_vm->_mouseButtons.pop_front();
314 
315 		int xp, yp;
316 		GetCursorXYNoWait(&xp, &yp, true);
317 		const Common::Point mousePos(xp, yp);
318 
319 		switch (type) {
320 		case Common::EVENT_LBUTTONDOWN:
321 			// left button press
322 			if (DwGetCurrentTime() - _ctx->lastLeftClick < (uint32)_vm->_config->_dclickSpeed) {
323 				// Left button double-click
324 
325 				if (TinselV2) {
326 					// Kill off the button process and fire off the action command
327 					CoroScheduler.killMatchingProcess(PID_BTN_CLICK, -1);
328 					PlayerEvent(PLR_ACTION, _ctx->clickPos);
329 				} else {
330 					// signal left drag start
331 					ProcessButEvent(PLR_DRAG1_START);
332 
333 					// signal left double click event
334 					ProcessButEvent(PLR_DLEFT);
335 				}
336 
337 				_ctx->lastLWasDouble = true;
338 			} else {
339 				// Initial mouse down - either for a single click, or potentially
340 				// the start of a double-click action
341 
342 				if (TinselV2) {
343 					PlayerEvent(PLR_DRAG1_START, mousePos);
344 
345 					ProvNotProcessed();
346 					PlayerEvent(PLR_PROV_WALKTO, mousePos);
347 
348 				} else {
349 					// signal left drag start
350 					ProcessButEvent(PLR_DRAG1_START);
351 
352 					// signal left single click event
353 					ProcessButEvent(PLR_SLEFT);
354 				}
355 
356 				_ctx->lastLWasDouble = false;
357 			}
358 			break;
359 
360 		case Common::EVENT_LBUTTONUP:
361 			// left button release
362 
363 			// update click timer
364 			if (_ctx->lastLWasDouble == false) {
365 				_ctx->lastLeftClick = DwGetCurrentTime();
366 
367 				// If player control is enabled, start a process which, if it times out,
368 				// will activate a single button click
369 				if (TinselV2 && ControlIsOn()) {
370 					_ctx->clickPos = mousePos;
371 					CoroScheduler.createProcess(PID_BTN_CLICK, SingleLeftProcess, &_ctx->clickPos, sizeof(Common::Point));
372 				}
373 			} else
374 				_ctx->lastLeftClick -= _vm->_config->_dclickSpeed;
375 
376 			if (TinselV2)
377 				// Signal left drag end
378 				PlayerEvent(PLR_DRAG1_END, mousePos);
379 			else
380 				// signal left drag end
381 				ProcessButEvent(PLR_DRAG1_END);
382 			break;
383 
384 		case Common::EVENT_RBUTTONDOWN:
385 			// right button press
386 
387 			if (DwGetCurrentTime() - _ctx->lastRightClick < (uint32)_vm->_config->_dclickSpeed) {
388 				// Right button double-click
389 				if (TinselV2) {
390 					PlayerEvent(PLR_NOEVENT, _ctx->clickPos);
391 				} else {
392 					// signal right drag start
393 					ProcessButEvent(PLR_DRAG2_START);
394 
395 					// signal right double click event
396 					ProcessButEvent(PLR_DRIGHT);
397 				}
398 
399 				_ctx->lastRWasDouble = true;
400 			} else {
401 				if (TinselV2) {
402 					PlayerEvent(PLR_DRAG2_START, mousePos);
403 					PlayerEvent(PLR_LOOK, mousePos);
404 				} else {
405 					// signal right drag start
406 					ProcessButEvent(PLR_DRAG2_START);
407 
408 					// signal right single click event
409 					ProcessButEvent(PLR_SRIGHT);
410 				}
411 
412 				_ctx->lastRWasDouble = false;
413 			}
414 			break;
415 
416 		case Common::EVENT_RBUTTONUP:
417 			// right button release
418 
419 			// update click timer
420 			if (_ctx->lastRWasDouble == false)
421 				_ctx->lastRightClick = DwGetCurrentTime();
422 			else
423 				_ctx->lastRightClick -= _vm->_config->_dclickSpeed;
424 
425 			if (TinselV2)
426 				// Signal left drag end
427 				PlayerEvent(PLR_DRAG2_END, mousePos);
428 			else
429 				// signal right drag end
430 				ProcessButEvent(PLR_DRAG2_END);
431 			break;
432 
433 		case Common::EVENT_WHEELUP:
434 			PlayerEvent(PLR_WHEEL_UP, mousePos);
435 			break;
436 
437 		case Common::EVENT_WHEELDOWN:
438 			PlayerEvent(PLR_WHEEL_DOWN, mousePos);
439 			break;
440 
441 		default:
442 			break;
443 		}
444 	}
445 	CORO_END_CODE;
446 }
447 
448 /**
449  * Run the master script.
450  * Continues between scenes, or until Interpret() returns.
451  */
MasterScriptProcess(CORO_PARAM,const void *)452 static void MasterScriptProcess(CORO_PARAM, const void *) {
453 	// COROUTINE
454 	CORO_BEGIN_CONTEXT;
455 		INT_CONTEXT *pic;
456 	CORO_END_CONTEXT(_ctx);
457 
458 	CORO_BEGIN_CODE(_ctx);
459 	_ctx->pic = InitInterpretContext(GS_MASTER, 0, NOEVENT, NOPOLY, 0, NULL);
460 	CORO_INVOKE_1(Interpret, _ctx->pic);
461 	CORO_END_CODE;
462 }
463 
464 /**
465  * Store the facts pertaining to a scene change.
466  */
SetNewScene(SCNHANDLE scene,int entrance,int transition)467 void SetNewScene(SCNHANDLE scene, int entrance, int transition) {
468 	if (!g_bCuttingScene && TinselV2)
469 		WrapScene();
470 
471 	// If we're loading from the GMM, load the scene as a delayed one
472 	if (g_loadingFromGMM) {
473 		g_DelayedScene.scene = scene;
474 		g_DelayedScene.entry = entrance;
475 		g_DelayedScene.trans = transition;
476 		g_loadingFromGMM = false;
477 		return;
478 	}
479 
480 	// If CD change will be required, stick in the scene change scene
481 	if (CdNumber(scene) != GetCurrentCD()) {
482 		// This scene gets delayed
483 		g_DelayedScene.scene = scene;
484 		g_DelayedScene.entry = entrance;
485 		g_DelayedScene.trans = transition;
486 
487 		g_NextScene.scene = g_hCdChangeScene;
488 		g_NextScene.entry = CdNumber(scene) - '0';
489 		g_NextScene.trans = TRANS_FADE;
490 
491 		return;
492 	}
493 
494 	if (g_HookScene.scene == 0 || g_bCuttingScene) {
495 		// This scene comes next
496 		g_NextScene.scene = scene;
497 		g_NextScene.entry = entrance;
498 		g_NextScene.trans = transition;
499 	} else {
500 		// This scene gets delayed
501 		g_DelayedScene.scene = scene;
502 		g_DelayedScene.entry = entrance;
503 		g_DelayedScene.trans = transition;
504 
505 		// The hooked scene comes next
506 		g_NextScene.scene = g_HookScene.scene;
507 		g_NextScene.entry = g_HookScene.entry;
508 		g_NextScene.trans = g_HookScene.trans;
509 
510 		g_HookScene.scene = 0;
511 	}
512 
513 	// Workaround for "Missing Red Dragon in square" bug in Discworld 1 PSX, act IV.
514 	// This happens with the original interpreter on PSX too: the red dragon in Act IV
515 	// doesn't show up inside the square at the right time. Original game required the
516 	// player to go in and out the square until the dragon appears (wasting hours).
517 	// I'm forcing the load of the right scene by checking that the player has (or has not) the
518 	// right items: player must have Mambo the swamp dragon, and mustn't have fireworks (used on
519 	// the swamp dragon previously to "load it up").
520 	if (TinselV1PSX && g_NextScene.scene == 0x1800000 && g_NextScene.entry == 2) {
521 		if ((IsInInventory(261, INV_1) || IsInInventory(261, INV_2)) &&
522 			(!IsInInventory(232, INV_1) && !IsInInventory(232, INV_2)))
523 			g_NextScene.entry = 1;
524 	}
525 }
526 
527 /**
528  * Store a scene as hooked
529  */
SetHookScene(SCNHANDLE scene,int entrance,int transition)530 void SetHookScene(SCNHANDLE scene, int entrance, int transition) {
531 	g_HookScene.scene = scene;
532 	g_HookScene.entry = entrance;
533 	g_HookScene.trans = transition;
534 }
535 
536 /**
537  * Hooked scene is over, trigger a change to the delayed scene
538  */
UnHookScene()539 void UnHookScene() {
540 	assert(g_DelayedScene.scene != 0); // no scene delayed
541 
542 	// The delayed scene can go now
543 	g_NextScene.scene = g_DelayedScene.scene;
544 	g_NextScene.entry = g_DelayedScene.entry;
545 	g_NextScene.trans = g_DelayedScene.trans;
546 
547 	g_DelayedScene.scene = 0;
548 }
549 
SuspendHook()550 void SuspendHook() {
551 	g_bCuttingScene = true;
552 }
553 
CdHasChanged()554 void CdHasChanged() {
555 	if (g_bChangingForRestore) {
556 		g_bChangingForRestore = false;
557 		RestoreGame(-2);
558 	} else {
559 		assert(g_DelayedScene.scene != 0);
560 
561 		WrapScene();
562 
563 		// The delayed scene can go now
564 		g_NextScene.scene = g_DelayedScene.scene;
565 		g_NextScene.entry = g_DelayedScene.entry;
566 		g_NextScene.trans = g_DelayedScene.trans;
567 
568 		g_DelayedScene.scene = 0;
569 	}
570 }
571 
SetCdChangeScene(SCNHANDLE hScene)572 void SetCdChangeScene(SCNHANDLE hScene) {
573 	g_hCdChangeScene = hScene;
574 }
575 
CDChangeForRestore(int cdNumber)576 void CDChangeForRestore(int cdNumber) {
577 	g_NextScene.scene = g_hCdChangeScene;
578 	g_NextScene.entry = cdNumber;
579 	g_NextScene.trans = TRANS_FADE;
580 	g_bChangingForRestore = true;
581 }
582 
UnSuspendHook()583 void UnSuspendHook() {
584 	g_bCuttingScene = false;
585 }
586 
syncSCdata(Common::Serializer & s)587 void syncSCdata(Common::Serializer &s) {
588 	s.syncAsUint32LE(g_HookScene.scene);
589 	s.syncAsSint32LE(g_HookScene.entry);
590 	s.syncAsSint32LE(g_HookScene.trans);
591 
592 	s.syncAsUint32LE(g_DelayedScene.scene);
593 	s.syncAsSint32LE(g_DelayedScene.entry);
594 	s.syncAsSint32LE(g_DelayedScene.trans);
595 }
596 
597 
598 //-----------------------------------------------------------------------
599 
RestoredProcess(CORO_PARAM,const void * param)600 static void RestoredProcess(CORO_PARAM, const void *param) {
601 	// COROUTINE
602 	CORO_BEGIN_CONTEXT;
603 		INT_CONTEXT *pic;
604 		bool bConverse;
605 	CORO_END_CONTEXT(_ctx);
606 
607 	CORO_BEGIN_CODE(_ctx);
608 
609 	// get the stuff copied to process when it was created
610 	_ctx->pic = *((INT_CONTEXT * const *)param);
611 
612 	_ctx->pic = RestoreInterpretContext(_ctx->pic);
613 	_ctx->bConverse = TinselV2 && (_ctx->pic->event == CONVERSE);
614 
615 	CORO_INVOKE_1(Interpret, _ctx->pic);
616 
617 	// Restore control after CallScene() from a conversation icon
618 	if (_ctx->bConverse)
619 		ControlOn();
620 
621 	CORO_END_CODE;
622 }
623 
RestoreProcess(INT_CONTEXT * pic)624 void RestoreProcess(INT_CONTEXT *pic) {
625 	CoroScheduler.createProcess(PID_TCODE, RestoredProcess, &pic, sizeof(pic));
626 }
627 
RestoreMasterProcess(INT_CONTEXT * pic)628 void RestoreMasterProcess(INT_CONTEXT *pic) {
629 	CoroScheduler.createProcess(PID_MASTER_SCR, RestoredProcess, &pic, sizeof(pic));
630 }
631 
632 // FIXME: CountOut is used by ChangeScene
633 // FIXME: Avoid non-const global vars
634 static int CountOut = 1;	// == 1 for immediate start of first scene
635 
636 /**
637  * If a scene restore is going on, just return (we don't update the
638  * screen during this time).
639  * If a scene change is required, 'order' the data for the new scene and
640  * start a fade out and a countdown.
641  * When the count expires, the screen will have faded. Ensure the scene	|
642  * is loaded, clear the screen, and start the new scene.
643  */
ChangeScene(bool bReset)644 bool ChangeScene(bool bReset) {
645 
646 	// Prevent attempt to fade-out when restarting game
647 	if (bReset) {
648 		CountOut = 1;	// immediate start of first scene again
649 		g_DelayedScene.scene = g_HookScene.scene = 0;
650 		return false;
651 	}
652 
653 	if (IsRestoringScene())
654 		return true;
655 
656 	if (g_NextScene.scene != 0) {
657 		if (!CountOut) {
658 			switch (g_NextScene.trans) {
659 			case TRANS_CUT:
660 				CountOut = 1;
661 				break;
662 
663 			case TRANS_FADE:
664 			default:
665 				// Trigger pre-load and fade and start countdown
666 				CountOut = COUNTOUT_COUNT;
667 				FadeOutFast();
668 				if (TinselV2)
669 					_vm->_pcmMusic->startFadeOut(COUNTOUT_COUNT);
670 				break;
671 			}
672 		} else if (--CountOut == 0) {
673 			if (!TinselV2)
674 				ClearScreen();
675 
676 			StartNewScene(g_NextScene.scene, g_NextScene.entry);
677 			g_NextScene.scene = 0;
678 
679 			switch (g_NextScene.trans) {
680 			case TRANS_CUT:
681 				SetDoFadeIn(false);
682 				break;
683 
684 			case TRANS_FADE:
685 			default:
686 				SetDoFadeIn(true);
687 				break;
688 			}
689 		} else
690 			_vm->_pcmMusic->fadeOutIteration();
691 	}
692 
693 	return false;
694 }
695 
696 /**
697  * CuttingScene
698  */
CuttingScene(bool bCutting)699 void CuttingScene(bool bCutting) {
700 	g_bCuttingScene = bCutting;
701 
702 	if (!bCutting)
703 		WrapScene();
704 }
705 
706 /**
707  * LoadBasicChunks
708  */
LoadBasicChunks()709 void LoadBasicChunks() {
710 	byte *cptr;
711 	int numObjects;
712 
713 	// Allocate RAM for savescene data
714 	InitializeSaveScenes();
715 
716 	// CHUNK_TOTAL_ACTORS seems to be missing in the released version, hard coding a value
717 	// TODO: Would be nice to just change 511 to MAX_SAVED_ALIVES
718 	cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_ACTORS);
719 	RegisterActors((cptr != NULL) ? READ_32(cptr) : 511);
720 
721 	// CHUNK_TOTAL_GLOBALS seems to be missing in some versions.
722 	// So if it is missing, set a reasonably high value for the number of globals.
723 	cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_GLOBALS);
724 	RegisterGlobals((cptr != NULL) ? READ_32(cptr) : 512);
725 
726 	cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_TOTAL_OBJECTS);
727 	numObjects = (cptr != NULL) ? READ_32(cptr) : 0;
728 
729 	cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_OBJECTS);
730 
731 	// Convert to native endianness
732 	INV_OBJECT *io = (INV_OBJECT *)cptr;
733 	for (int i = 0; i < numObjects; i++, io++) {
734 		io->id        = FROM_32(io->id);
735 		io->hIconFilm = FROM_32(io->hIconFilm);
736 		io->hScript   = FROM_32(io->hScript);
737 		io->attribute = FROM_32(io->attribute);
738 	}
739 
740 	RegisterIcons(cptr, numObjects);
741 
742 	cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_POLY);
743 	// Max polygons are 0 in DW1 Mac (both in the demo and the full version)
744 	if (cptr != NULL && *cptr != 0)
745 		MaxPolygons(*cptr);
746 
747 	if (TinselV2) {
748 		// Global processes
749 		cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_NUM_PROCESSES);
750 		assert(cptr && (*cptr < 100));
751 		int num = *cptr;
752 		cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_PROCESSES);
753 		assert(!num || cptr);
754 		GlobalProcesses(num, cptr);
755 
756 		// CdPlay() stuff
757 		cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_CDPLAY_HANDLE);
758 		assert(cptr);
759 		uint32 playHandle = READ_32(cptr);
760 		assert(playHandle < 512);
761 		SetCdPlayHandle(playHandle);
762 	}
763 }
764 
765 //----------------- TinselEngine --------------------
766 
767 // Global pointer to engine
768 TinselEngine *_vm;
769 
770 struct GameSettings {
771 	const char *gameid;
772 	const char *description;
773 	byte id;
774 	uint32 features;
775 	const char *detectname;
776 };
777 
778 static const GameSettings tinselSettings[] = {
779 	{"tinsel", "Tinsel game", 0, 0, 0},
780 
781 	{NULL, NULL, 0, 0, NULL}
782 };
783 
784 // For the languages, refer to the LANGUAGE enum in dw.h
785 
786 const char *const TinselEngine::_sampleIndices[][3] = {
787 	{ "english.idx", "english1.idx", "english2.idx" },	// English
788 	{ "french.idx", "french1.idx", "french2.idx" },		// French
789 	{ "german.idx", "german1.idx", "german2.idx" },		// German
790 	{ "english.idx", "english1.idx", "english2.idx" },	// Italian
791 	{ "english.idx", "english1.idx", "english2.idx" },	// Spanish
792 	{ "english.idx", "english1.idx", "english2.idx" },	// Hebrew (FIXME: not sure if this is correct)
793 	{ "english.idx", "english1.idx", "english2.idx" },	// Hungarian (FIXME: not sure if this is correct)
794 	{ "english.idx", "english1.idx", "english2.idx" },	// Japanese (FIXME: not sure if this is correct)
795 	{ "us.idx", "us1.idx", "us2.idx" }					// US English
796 };
797 const char *const TinselEngine::_sampleFiles[][3] = {
798 	{ "english.smp", "english1.smp", "english2.smp" },	// English
799 	{ "french.smp", "french1.smp", "french2.smp" },		// French
800 	{ "german.smp", "german1.smp", "german2.smp" },		// German
801 	{ "english.smp", "english1.smp", "english2.smp" },	// Italian
802 	{ "english.smp", "english1.smp", "english2.smp" },	// Spanish
803 	{ "english.smp", "english1.smp", "english2.smp" },	// Hebrew (FIXME: not sure if this is correct)
804 	{ "english.smp", "english1.smp", "english2.smp" },	// Hungarian (FIXME: not sure if this is correct)
805 	{ "english.smp", "english1.smp", "english2.smp" },	// Japanese (FIXME: not sure if this is correct)
806 	{ "us.smp", "us1.smp", "us2.smp" },					// US English
807 };
808 const char *const TinselEngine::_textFiles[][3] = {
809 	{ "english.txt", "english1.txt", "english2.txt" },	// English
810 	{ "french.txt", "french1.txt", "french2.txt" },		// French
811 	{ "german.txt", "german1.txt", "german2.txt" },		// German
812 	{ "italian.txt", "italian1.txt", "italian2.txt" },	// Italian
813 	{ "spanish.txt", "spanish1.txt", "spanish2.txt" },	// Spanish
814 	{ "english.txt", "english1.txt", "english2.txt" },	// Hebrew (FIXME: not sure if this is correct)
815 	{ "english.txt", "english1.txt", "english2.txt" },	// Hungarian (FIXME: not sure if this is correct)
816 	{ "english.txt", "english1.txt", "english2.txt" },	// Japanese (FIXME: not sure if this is correct)
817 	{ "us.txt", "us1.txt", "us2.txt" }					// US English
818 };
819 
820 
TinselEngine(OSystem * syst,const TinselGameDescription * gameDesc)821 TinselEngine::TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc) :
822 		Engine(syst), _gameDescription(gameDesc), _random("tinsel"),
823 		_console(0), _sound(0), _midiMusic(0), _pcmMusic(0), _bmv(0) {
824 	// Register debug flags
825 	DebugMan.addDebugChannel(kTinselDebugAnimations, "animations", "Animations debugging");
826 	DebugMan.addDebugChannel(kTinselDebugActions, "actions", "Actions debugging");
827 	DebugMan.addDebugChannel(kTinselDebugSound, "sound", "Sound debugging");
828 	DebugMan.addDebugChannel(kTinselDebugMusic, "music", "Music debugging");
829 
830 	_vm = this;
831 
832 	_gameId = 0;
833 	_driver = NULL;
834 
835 	_config = new Config(this);
836 
837 	// Setup mixer
838 	syncSoundSettings();
839 
840 	const GameSettings *g;
841 
842 	const char *gameid = ConfMan.get("gameid").c_str();
843 	for (g = tinselSettings; g->gameid; ++g)
844 		if (!scumm_stricmp(g->gameid, gameid))
845 			_gameId = g->id;
846 
847 	_system->getAudioCDManager()->open();
848 
849 	_mousePos.x = 0;
850 	_mousePos.y = 0;
851 	_keyHandler = NULL;
852 	_dosPlayerDir = 0;
853 }
854 
~TinselEngine()855 TinselEngine::~TinselEngine() {
856 	_system->getAudioCDManager()->stop();
857 	delete _bmv;
858 	delete _sound;
859 	delete _midiMusic;
860 	delete _pcmMusic;
861 	delete _console;
862 	_screenSurface.free();
863 	FreeSaveScenes();
864 	FreeTextBuffer();
865 	FreeHandleTable();
866 	FreeActors();
867 	FreeObjectList();
868 	FreeGlobalProcesses();
869 	FreeGlobals();
870 
871 	delete _config;
872 
873 	MemoryDeinit();
874 }
875 
getSavegameFilename(int16 saveNum) const876 Common::String TinselEngine::getSavegameFilename(int16 saveNum) const {
877 	return Common::String::format("%s.%03d", getTargetName().c_str(), saveNum);
878 }
879 
initializePath(const Common::FSNode & gamePath)880 void TinselEngine::initializePath(const Common::FSNode &gamePath) {
881 	if (TinselV1PSX) {
882 		// Add subfolders needed for psx versions of Discworld 1
883 		SearchMan.addDirectory(gamePath.getPath(), gamePath, 0, 3, true);
884 	} else {
885 		// Add DW2 subfolder to search path in case user is running directly from the CDs
886 		SearchMan.addSubDirectoryMatching(gamePath, "dw2");
887 
888 		// Location of Miles audio files (sample.ad and sample.opl) in Discworld 1
889 		SearchMan.addSubDirectoryMatching(gamePath, "drivers");
890 		Engine::initializePath(gamePath);
891 	}
892 }
893 
run()894 Common::Error TinselEngine::run() {
895 	_midiMusic = new MidiMusicPlayer(this);
896 	_pcmMusic = new PCMMusicPlayer();
897 	_sound = new SoundManager(this);
898 	_bmv = new BMVPlayer();
899 
900 	// Initialize backend
901 	if (getGameID() == GID_DW2) {
902 #ifndef DW2_EXACT_SIZE
903 		initGraphics(640, 480);
904 #else
905 		initGraphics(640, 432);
906 #endif
907 		_screenSurface.create(640, 432, Graphics::PixelFormat::createFormatCLUT8());
908 	} else {
909 		initGraphics(320, 200);
910 		_screenSurface.create(320, 200, Graphics::PixelFormat::createFormatCLUT8());
911 	}
912 
913 	_console = new Console();
914 
915 	CoroScheduler.reset();
916 
917 	InitSysVars();
918 
919 	// init memory manager
920 	MemoryInit();
921 
922 	// load user configuration
923 	_vm->_config->readFromDisk();
924 
925 #if 1
926 	// FIXME: The following is taken from RestartGame().
927 	// It may have to be adjusted a bit
928 	CountOut = 1;
929 
930 	RebootCursor();
931 	RebootDeadTags();
932 	RebootMovers();
933 	resetUserEventTime();
934 	RebootTimers();
935 	RebootScalingReels();
936 
937 	g_DelayedScene.scene = g_HookScene.scene = 0;
938 #endif
939 
940 	// Load in text strings
941 	ChangeLanguage(_vm->_config->_language);
942 
943 	// Init palette and object managers, scheduler, keyboard and mouse
944 	RestartDrivers();
945 
946 	// load in graphics info
947 	SetupHandleTable();
948 
949 	// Actors, globals and inventory icons
950 	LoadBasicChunks();
951 
952 	// Continuous game processes
953 	CreateConstProcesses();
954 
955 	// allow game to run in the background
956 	//RestartBackgroundProcess();	// FIXME: is this still needed?
957 
958 	//dumpMusic();	// dumps all of the game's music in external XMIDI files
959 
960 	// Load game from specified slot, if any
961 	//
962 	// TODO: We might want to think about properly taking care of possible
963 	// errors when loading the save state.
964 
965 	if (ConfMan.hasKey("save_slot")) {
966 		if (loadGameState(ConfMan.getInt("save_slot")).getCode() == Common::kNoError)
967 			g_loadingFromGMM = true;
968 	}
969 
970 	// Foreground loop
971 	uint32 timerVal = 0;
972 	while (!shouldQuit()) {
973 		assert(_console);
974 		_console->onFrame();
975 
976 		// Check for time to do next game cycle
977 		if ((g_system->getMillis() > timerVal + GAME_FRAME_DELAY)) {
978 			timerVal = g_system->getMillis();
979 			_system->getAudioCDManager()->update();
980 			NextGameCycle();
981 		}
982 
983 		if (g_bRestart) {
984 			RestartGame();
985 			g_bRestart = false;
986 			g_bHasRestarted = true;	// Set restarted flag
987 		}
988 
989 		// Save/Restore scene file transfers
990 		ProcessSRQueue();
991 
992 		// Handle any playing movie
993 		_bmv->FettleBMV();
994 
995 #ifdef DEBUG
996 		if (g_bFast)
997 			continue;		// run flat-out
998 #endif
999 		// Loop processing events while there are any pending
1000 		while (pollEvent())
1001 			;
1002 
1003 		DoCdChange();
1004 
1005 		if (_bmv->MoviePlaying() && _bmv->NextMovieTime())
1006 			g_system->delayMillis(MAX<int>(_bmv->NextMovieTime() - g_system->getMillis() + _bmv->MovieAudioLag(), 0));
1007 		else
1008 			g_system->delayMillis(10);
1009 	}
1010 
1011 	if (_bmv->MoviePlaying())
1012 		_bmv->FinishBMV();
1013 
1014 	// Write configuration
1015 	_vm->_config->writeToDisk();
1016 
1017 	EndScene();
1018 	g_pCurBgnd = NULL;
1019 
1020 	return Common::kNoError;
1021 }
1022 
1023 
NextGameCycle()1024 void TinselEngine::NextGameCycle() {
1025 	// Dim Music
1026 	_pcmMusic->dimIteration();
1027 
1028 	// Check for scene change
1029 	ChangeScene(false);
1030 
1031 	// Allow a user event for this schedule
1032 	ResetEcount();
1033 
1034 	// schedule process
1035 	CoroScheduler.schedule();
1036 
1037 	if (_bmv->MoviePlaying())
1038 		_bmv->CopyMovieToScreen();
1039 	else
1040 		// redraw background
1041 		DrawBackgnd();
1042 
1043 	// Why waste resources on yet another process?
1044 	FettleTimers();
1045 }
1046 
1047 
pollEvent()1048 bool TinselEngine::pollEvent() {
1049 	Common::Event event;
1050 
1051 	if (!g_system->getEventManager()->pollEvent(event))
1052 		return false;
1053 
1054 	// Handle the various kind of events
1055 	switch (event.type) {
1056 	case Common::EVENT_LBUTTONDOWN:
1057 	case Common::EVENT_LBUTTONUP:
1058 	case Common::EVENT_RBUTTONDOWN:
1059 	case Common::EVENT_RBUTTONUP:
1060 	case Common::EVENT_WHEELUP:
1061 	case Common::EVENT_WHEELDOWN:
1062 		// Add button to queue for the mouse process
1063 		_mouseButtons.push_back(event.type);
1064 		break;
1065 
1066 	case Common::EVENT_MOUSEMOVE:
1067 		{
1068 			// This fragment takes care of Tinsel 2 when it's been compiled with
1069 			// blank areas at the top and bottom of the screen
1070 			int ySkip = TinselV2 ? (g_system->getHeight() - _vm->screen().h) / 2 : 0;
1071 			if ((event.mouse.y >= ySkip) && (event.mouse.y < (g_system->getHeight() - ySkip)))
1072 				_mousePos = Common::Point(event.mouse.x, event.mouse.y - ySkip);
1073 		}
1074 		break;
1075 
1076 	case Common::EVENT_KEYDOWN:
1077 	case Common::EVENT_KEYUP:
1078 		ProcessKeyEvent(event);
1079 		break;
1080 
1081 	default:
1082 		break;
1083 	}
1084 
1085 	return true;
1086 }
1087 
1088 /**
1089  * Start the processes that continue between scenes.
1090  */
CreateConstProcesses()1091 void TinselEngine::CreateConstProcesses() {
1092 	// Process to run the master script
1093 	CoroScheduler.createProcess(PID_MASTER_SCR, MasterScriptProcess, NULL, 0);
1094 
1095 	// Processes to run the cursor and inventory,
1096 	CoroScheduler.createProcess(PID_CURSOR, CursorProcess, NULL, 0);
1097 	CoroScheduler.createProcess(PID_INVENTORY, InventoryProcess, NULL, 0);
1098 }
1099 
1100 /**
1101  * Restart the game
1102  */
RestartGame()1103 void TinselEngine::RestartGame() {
1104 	HoldItem(INV_NOICON);	// Holding nothing
1105 
1106 	DropBackground();	// No background
1107 
1108 	// Ditches existing infrastructure background
1109 	PrimeBackground();
1110 
1111 	// Next scene change won't need to fade out
1112 	// -> reset the count used by ChangeScene
1113 	CountOut = 1;
1114 
1115 	RebootCursor();
1116 	RebootDeadTags();
1117 	RebootMovers();
1118 	RebootTimers();
1119 	RebootScalingReels();
1120 
1121 	g_DelayedScene.scene = g_HookScene.scene = 0;
1122 
1123 	// remove keyboard, mouse and joystick drivers
1124 	ChopDrivers();
1125 
1126 	// Init palette and object managers, scheduler, keyboard and mouse
1127 	RestartDrivers();
1128 
1129 	// Actors, globals and inventory icons
1130 	LoadBasicChunks();
1131 
1132 	// Continuous game processes
1133 	CreateConstProcesses();
1134 }
1135 
1136 /**
1137  * Init palette and object managers, scheduler, keyboard and mouse.
1138  */
RestartDrivers()1139 void TinselEngine::RestartDrivers() {
1140 	// init the palette manager
1141 	ResetPalAllocator();
1142 
1143 	// init the object manager
1144 	KillAllObjects();
1145 
1146 	// init the process scheduler
1147 	CoroScheduler.reset();
1148 
1149 	// init the event handlers
1150 	g_pMouseProcess = CoroScheduler.createProcess(PID_MOUSE, MouseProcess, NULL, 0);
1151 	g_pKeyboardProcess = CoroScheduler.createProcess(PID_KEYBOARD, KeyboardProcess, NULL, 0);
1152 
1153 	// open MIDI files
1154 	OpenMidiFiles();
1155 
1156 	// open sample files (only if mixer is ready)
1157 	if (_mixer->isReady()) {
1158 		_sound->openSampleFiles();
1159 	}
1160 
1161 	// Set midi volume
1162 	bool mute = false;
1163 	if (ConfMan.hasKey("mute"))
1164 		mute = ConfMan.getBool("mute");
1165 
1166 	SetMidiVolume(mute ? 0 : _vm->_config->_musicVolume);
1167 }
1168 
1169 /**
1170  * Remove keyboard, mouse and joystick drivers.
1171  */
ChopDrivers()1172 void TinselEngine::ChopDrivers() {
1173 	// remove sound driver
1174 	StopMidi();
1175 	_sound->stopAllSamples();
1176 	DeleteMidiBuffer();
1177 
1178 	// remove event drivers
1179 	CoroScheduler.killProcess(g_pMouseProcess);
1180 	CoroScheduler.killProcess(g_pKeyboardProcess);
1181 }
1182 
1183 /**
1184  * Process a keyboard event
1185  */
ProcessKeyEvent(const Common::Event & event)1186 void TinselEngine::ProcessKeyEvent(const Common::Event &event) {
1187 
1188 	// Handle any special keys immediately
1189 	switch (event.kbd.keycode) {
1190 	case Common::KEYCODE_d:
1191 		// Checks for CTRL flag, ignoring all the sticky flags
1192 		if (event.kbd.hasFlags(Common::KBD_CTRL) && event.type == Common::EVENT_KEYDOWN) {
1193 			// Activate the debugger
1194 			assert(_console);
1195 			_console->attach();
1196 			return;
1197 		}
1198 		break;
1199 	default:
1200 		break;
1201 	}
1202 
1203 	// Check for movement keys
1204 	int idx = 0;
1205 	switch (event.kbd.keycode) {
1206 	case Common::KEYCODE_UP:
1207 	case Common::KEYCODE_KP8:
1208 		idx = MSK_UP;
1209 		break;
1210 	case Common::KEYCODE_DOWN:
1211 	case Common::KEYCODE_KP2:
1212 		idx = MSK_DOWN;
1213 		break;
1214 	case Common::KEYCODE_LEFT:
1215 	case Common::KEYCODE_KP4:
1216 		idx = MSK_LEFT;
1217 		break;
1218 	case Common::KEYCODE_RIGHT:
1219 	case Common::KEYCODE_KP6:
1220 		idx = MSK_RIGHT;
1221 		break;
1222 	default:
1223 		break;
1224 	}
1225 	if (idx != 0) {
1226 		if (event.type == Common::EVENT_KEYDOWN)
1227 			_dosPlayerDir |= idx;
1228 		else
1229 			_dosPlayerDir &= ~idx;
1230 		return;
1231 	}
1232 
1233 	// All other keypresses add to the queue for processing in KeyboardProcess
1234 	_keypresses.push_back(event);
1235 }
1236 
getSampleIndex(LANGUAGE lang)1237 const char *TinselEngine::getSampleIndex(LANGUAGE lang) {
1238 	int cd;
1239 
1240 	if (TinselV2) {
1241 		cd = GetCurrentCD();
1242 		assert((cd == 1) || (cd == 2));
1243 		assert(((unsigned int) lang) < NUM_LANGUAGES);
1244 
1245 		if (lang == TXT_ENGLISH)
1246 			if (_vm->getLanguage() == Common::EN_USA)
1247 				lang = TXT_US;
1248 
1249 	} else {
1250 		cd = 0;
1251 		lang = TXT_ENGLISH;
1252 	}
1253 
1254 	return _sampleIndices[lang][cd];
1255 }
1256 
getSampleFile(LANGUAGE lang)1257 const char *TinselEngine::getSampleFile(LANGUAGE lang) {
1258 	int cd;
1259 
1260 	if (TinselV2) {
1261 		cd = GetCurrentCD();
1262 		assert((cd == 1) || (cd == 2));
1263 		assert(((unsigned int) lang) < NUM_LANGUAGES);
1264 
1265 		if (lang == TXT_ENGLISH)
1266 			if (_vm->getLanguage() == Common::EN_USA)
1267 				lang = TXT_US;
1268 
1269 	} else {
1270 		cd = 0;
1271 		lang = TXT_ENGLISH;
1272 	}
1273 
1274 	return _sampleFiles[lang][cd];
1275 }
1276 
getTextFile(LANGUAGE lang)1277 const char *TinselEngine::getTextFile(LANGUAGE lang) {
1278 	assert(((unsigned int) lang) < NUM_LANGUAGES);
1279 
1280 	int cd;
1281 
1282 	if (TinselV2) {
1283 		cd = GetCurrentCD();
1284 		assert((cd == 1) || (cd == 2));
1285 
1286 		if (lang == TXT_ENGLISH)
1287 			if (_vm->getLanguage() == Common::EN_USA)
1288 				lang = TXT_US;
1289 
1290 	} else
1291 		cd = 0;
1292 
1293 	return _textFiles[lang][cd];
1294 }
1295 
1296 } // End of namespace Tinsel
1297