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