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/config-manager.h"
24 #include "common/debug-channels.h"
25 #include "common/system.h"
26 #include "common/translation.h"
27 #include "common/textconsole.h"
28
29 #include "gui/saveload.h"
30
31 #include "mohawk/cursors.h"
32 #include "mohawk/myst.h"
33 #include "mohawk/myst_areas.h"
34 #include "mohawk/myst_card.h"
35 #include "mohawk/myst_graphics.h"
36 #include "mohawk/myst_scripts.h"
37 #include "mohawk/myst_sound.h"
38 #include "mohawk/myst_state.h"
39 #include "mohawk/dialogs.h"
40 #include "mohawk/resource.h"
41 #include "mohawk/resource_cache.h"
42 #include "mohawk/video.h"
43
44 // The stacks
45 #include "mohawk/myst_stacks/channelwood.h"
46 #include "mohawk/myst_stacks/credits.h"
47 #include "mohawk/myst_stacks/demo.h"
48 #include "mohawk/myst_stacks/dni.h"
49 #include "mohawk/myst_stacks/intro.h"
50 #include "mohawk/myst_stacks/makingof.h"
51 #include "mohawk/myst_stacks/mechanical.h"
52 #include "mohawk/myst_stacks/menu.h"
53 #include "mohawk/myst_stacks/myst.h"
54 #include "mohawk/myst_stacks/preview.h"
55 #include "mohawk/myst_stacks/selenitic.h"
56 #include "mohawk/myst_stacks/slides.h"
57 #include "mohawk/myst_stacks/stoneship.h"
58
59 namespace Mohawk {
60
MohawkEngine_Myst(OSystem * syst,const MohawkGameDescription * gamedesc)61 MohawkEngine_Myst::MohawkEngine_Myst(OSystem *syst, const MohawkGameDescription *gamedesc) :
62 MohawkEngine(syst, gamedesc) {
63 DebugMan.addDebugChannel(kDebugVariable, "Variable", "Track Variable Accesses");
64 DebugMan.addDebugChannel(kDebugSaveLoad, "SaveLoad", "Track Save/Load Function");
65 DebugMan.addDebugChannel(kDebugView, "View", "Track Card File (VIEW) Parsing");
66 DebugMan.addDebugChannel(kDebugHint, "Hint", "Track Cursor Hints (HINT) Parsing");
67 DebugMan.addDebugChannel(kDebugResource, "Resource", "Track Resource (RLST) Parsing");
68 DebugMan.addDebugChannel(kDebugINIT, "Init", "Track Card Init Script (INIT) Parsing");
69 DebugMan.addDebugChannel(kDebugEXIT, "Exit", "Track Card Exit Script (EXIT) Parsing");
70 DebugMan.addDebugChannel(kDebugScript, "Script", "Track Script Execution");
71 DebugMan.addDebugChannel(kDebugHelp, "Help", "Track Help File (HELP) Parsing");
72 DebugMan.addDebugChannel(kDebugCache, "Cache", "Track Resource Cache Accesses");
73
74 _currentCursor = 0;
75 _mainCursor = kDefaultMystCursor;
76 _showResourceRects = false;
77 _lastSaveTime = 0;
78
79 _sound = nullptr;
80 _video = nullptr;
81 _gfx = nullptr;
82 _console = nullptr;
83 _gameState = nullptr;
84 _optionsDialog = nullptr;
85 _rnd = nullptr;
86
87 _mouseClicked = false;
88 _mouseMoved = false;
89 _escapePressed = false;
90 _waitingOnBlockingOperation = false;
91 }
92
~MohawkEngine_Myst()93 MohawkEngine_Myst::~MohawkEngine_Myst() {
94 DebugMan.clearAllDebugChannels();
95
96 delete _gfx;
97 delete _video;
98 delete _sound;
99 delete _console;
100 delete _gameState;
101 delete _optionsDialog;
102 delete _rnd;
103 }
104
105 // Uses cached data objects in preference to disk access
getResource(uint32 tag,uint16 id)106 Common::SeekableReadStream *MohawkEngine_Myst::getResource(uint32 tag, uint16 id) {
107 Common::SeekableReadStream *ret = _cache.search(tag, id);
108
109 if (ret)
110 return ret;
111
112 for (uint32 i = 0; i < _mhk.size(); i++)
113 if (_mhk[i]->hasResource(tag, id)) {
114 ret = _mhk[i]->getResource(tag, id);
115 _cache.add(tag, id, ret);
116 return ret;
117 }
118
119 error("Could not find a \'%s\' resource with ID %04x", tag2str(tag), id);
120 }
121
getResourceIDList(uint32 type) const122 Common::Array<uint16> MohawkEngine_Myst::getResourceIDList(uint32 type) const {
123 Common::Array<uint16> ids;
124
125 for (uint i = 0; i < _mhk.size(); i++) {
126 ids.push_back(_mhk[i]->getResourceIDList(type));
127 }
128
129 return ids;
130 }
131
cachePreload(uint32 tag,uint16 id)132 void MohawkEngine_Myst::cachePreload(uint32 tag, uint16 id) {
133 if (!_cache.enabled)
134 return;
135
136 for (uint32 i = 0; i < _mhk.size(); i++) {
137 // Check for MJMP in Myst ME
138 if ((getFeatures() & GF_ME) && tag == ID_MSND && _mhk[i]->hasResource(ID_MJMP, id)) {
139 Common::SeekableReadStream *tempData = _mhk[i]->getResource(ID_MJMP, id);
140 uint16 msndId = tempData->readUint16LE();
141 delete tempData;
142
143 // We've found where the real MSND data is, so go get that
144 tempData = _mhk[i]->getResource(tag, msndId);
145 _cache.add(tag, id, tempData);
146 delete tempData;
147 return;
148 }
149
150 if (_mhk[i]->hasResource(tag, id)) {
151 Common::SeekableReadStream *tempData = _mhk[i]->getResource(tag, id);
152 _cache.add(tag, id, tempData);
153 delete tempData;
154 return;
155 }
156 }
157
158 debugC(kDebugCache, "cachePreload: Could not find a \'%s\' resource with ID %04x", tag2str(tag), id);
159 }
160
161 static const char *mystFiles[] = {
162 "channel",
163 "credits",
164 "demo",
165 "dunny",
166 "intro",
167 "making",
168 "mechan",
169 "myst",
170 "selen",
171 "slides",
172 "sneak",
173 "stone",
174 "menu"
175 };
176
177 // Myst Hardcoded Movie Paths
178 // Mechanical Stack Movie "sstairs" referenced in executable, but not used?
179
180 // NOTE: cl1wg1.mov etc. found in the root directory in versions of Myst
181 // Original are duplicates of those in qtw/myst directory and thus not necessary.
182 // However, this *is* a problem for Myst ME Mac. Right now it will use the qtw/myst
183 // video, but this is most likely going to fail for the standalone Mac version.
184
185 // The following movies are not referenced in RLST or hardcoded into the executables.
186 // It is likely they are unused:
187 // qtw/mech/lwrgear2.mov + lwrgears.mov: I have no idea what these are; perhaps replaced by an animated image in-game?
188 // qtw/myst/gar4wbf1.mov: gar4wbf2.mov has two butterflies instead of one
189 // qtw/myst/libelev.mov: libup.mov is basically the same with sound
190
wrapMovieFilename(const Common::String & movieName,uint16 stack)191 Common::String MohawkEngine_Myst::wrapMovieFilename(const Common::String &movieName, uint16 stack) {
192 Common::String prefix;
193
194 switch (stack) {
195 case kIntroStack:
196 prefix = "intro/";
197 break;
198 case kChannelwoodStack:
199 // The Windmill videos like to hide in a different folder
200 if (movieName.contains("wmill"))
201 prefix = "channel2/";
202 else
203 prefix = "channel/";
204 break;
205 case kDniStack:
206 prefix = "dunny/";
207 break;
208 case kMechanicalStack:
209 prefix = "mech/";
210 break;
211 case kMystStack:
212 prefix = "myst/";
213 break;
214 case kSeleniticStack:
215 prefix = "selen/";
216 break;
217 case kStoneshipStack:
218 prefix = "stone/";
219 break;
220 default:
221 // Masterpiece Edition Only Movies
222 break;
223 }
224
225 return Common::String("qtw/") + prefix + movieName + ".mov";
226 }
227
selectLocalizedMovieFilename(const Common::String & movieName)228 Common::String MohawkEngine_Myst::selectLocalizedMovieFilename(const Common::String &movieName) {
229 Common::String language;
230 if (getFeatures() & GF_LANGUAGE_FILES) {
231 language = getDatafileLanguageName("myst_");
232 }
233
234 Common::String localizedMovieName = Common::String::format("%s/%s", language.c_str(), movieName.c_str());
235 if (!language.empty() && SearchMan.hasFile(localizedMovieName)) {
236 return localizedMovieName;
237 } else {
238 return movieName;
239 }
240 }
241
playMovie(const Common::String & name,MystStack stack)242 VideoEntryPtr MohawkEngine_Myst::playMovie(const Common::String &name, MystStack stack) {
243 Common::String filename = wrapMovieFilename(name, stack);
244 filename = selectLocalizedMovieFilename(filename);
245 VideoEntryPtr video = _video->playMovie(filename, Audio::Mixer::kSFXSoundType);
246
247 if (!video) {
248 error("Failed to open the '%s' movie", filename.c_str());
249 }
250
251 return video;
252 }
253
playMovieFullscreen(const Common::String & name,MystStack stack)254 VideoEntryPtr MohawkEngine_Myst::playMovieFullscreen(const Common::String &name, MystStack stack) {
255 _gfx->clearScreen();
256
257 VideoEntryPtr video = playMovie(name, stack);
258 video->center();
259 return video;
260 }
261
262
findVideo(const Common::String & name,MystStack stack)263 VideoEntryPtr MohawkEngine_Myst::findVideo(const Common::String &name, MystStack stack) {
264 Common::String filename = wrapMovieFilename(name, stack);
265 filename = selectLocalizedMovieFilename(filename);
266 return _video->findVideo(filename);
267 }
268
playMovieBlocking(const Common::String & name,MystStack stack,uint16 x,uint16 y)269 void MohawkEngine_Myst::playMovieBlocking(const Common::String &name, MystStack stack, uint16 x, uint16 y) {
270 Common::String filename = wrapMovieFilename(name, stack);
271 filename = selectLocalizedMovieFilename(filename);
272 VideoEntryPtr video = _video->playMovie(filename, Audio::Mixer::kSFXSoundType);
273 if (!video) {
274 error("Failed to open the '%s' movie", filename.c_str());
275 }
276
277 video->moveTo(x, y);
278
279 waitUntilMovieEnds(video);
280 }
281
playFlybyMovie(MystStack stack)282 void MohawkEngine_Myst::playFlybyMovie(MystStack stack) {
283 static const uint16 kMasterpieceOnly = 0xFFFF;
284
285 // Play Flyby Entry Movie on Masterpiece Edition.
286 const char *flyby = nullptr;
287 bool looping = true;
288
289 switch (stack) {
290 case kSeleniticStack:
291 flyby = "selenitic flyby";
292 break;
293 case kStoneshipStack:
294 flyby = "stoneship flyby";
295 break;
296 // Myst Flyby Movie not used in Original Masterpiece Edition Engine
297 // We play it when first arriving on Myst, and if the user has chosen so.
298 case kMystStack:
299 if (ConfMan.getBool("playmystflyby")) {
300 flyby = "myst flyby";
301 looping = false;
302 }
303 break;
304 case kMechanicalStack:
305 flyby = "mech age flyby";
306 break;
307 case kChannelwoodStack:
308 flyby = "channelwood flyby";
309 break;
310 default:
311 break;
312 }
313
314 if (!flyby) {
315 return;
316 }
317
318 _gfx->clearScreen();
319
320 Common::String filename = wrapMovieFilename(flyby, kMasterpieceOnly);
321 VideoEntryPtr video = _video->playMovie(filename, Audio::Mixer::kSFXSoundType);
322 if (!video) {
323 error("Failed to open the '%s' movie", filename.c_str());
324 }
325
326 video->center();
327 playSkippableMovie(video, looping);
328 }
329
playSkippableMovie(const VideoEntryPtr & video,bool looping)330 void MohawkEngine_Myst::playSkippableMovie(const VideoEntryPtr &video, bool looping) {
331 _waitingOnBlockingOperation = true;
332
333 video->setLooping(true);
334
335 _cursor->setCursor(_mainCursor);
336
337 while ((looping || !video->endOfVideo()) && !shouldQuit()) {
338 doFrame();
339
340 // Allow skipping
341 if (_escapePressed) {
342 _escapePressed = false;
343 break;
344 }
345
346 if (_mouseClicked) {
347 _mouseClicked = false;
348 break;
349 }
350 }
351
352 _cursor->setCursor(0);
353
354 // Ensure it's removed
355 _video->removeEntry(video);
356 _waitingOnBlockingOperation = false;
357 }
358
waitUntilMovieEnds(const VideoEntryPtr & video)359 void MohawkEngine_Myst::waitUntilMovieEnds(const VideoEntryPtr &video) {
360 if (!video)
361 return;
362
363 _waitingOnBlockingOperation = true;
364
365 // Sanity check
366 if (video->isLooping())
367 error("Called waitUntilMovieEnds() on a looping video");
368
369 while (!video->endOfVideo() && !shouldQuit()) {
370 doFrame();
371
372 // Allow skipping
373 if (_escapePressed) {
374 _escapePressed = false;
375 break;
376 }
377 }
378
379 // Ensure it's removed
380 _video->removeEntry(video);
381 _waitingOnBlockingOperation = false;
382 }
383
playSoundBlocking(uint16 id)384 void MohawkEngine_Myst::playSoundBlocking(uint16 id) {
385 _waitingOnBlockingOperation = true;
386 _sound->playEffect(id);
387
388 while (_sound->isEffectPlaying() && !shouldQuit()) {
389 doFrame();
390 }
391 _waitingOnBlockingOperation = false;
392 }
393
run()394 Common::Error MohawkEngine_Myst::run() {
395 MohawkEngine::run();
396
397 if (!_mixer->isReady()) {
398 return Common::kAudioDeviceInitFailed;
399 }
400
401 ConfMan.registerDefault("zip_mode", false);
402 ConfMan.registerDefault("transition_mode", false);
403
404 _gfx = new MystGraphics(this);
405 _video = new VideoManager(this);
406 _sound = new MystSound(this);
407 _console = new MystConsole(this);
408 _gameState = new MystGameState(this, _saveFileMan);
409 _optionsDialog = new MystOptionsDialog(this);
410 _cursor = new MystCursorManager(this);
411 _rnd = new Common::RandomSource("myst");
412
413 // Cursor is visible by default
414 _cursor->showCursor();
415
416 // Load game from launcher/command line if requested
417 if (ConfMan.hasKey("save_slot") && hasGameSaveSupport()) {
418 int saveSlot = ConfMan.getInt("save_slot");
419 if (!_gameState->load(saveSlot))
420 error("Failed to load save game from slot %i", saveSlot);
421 } else {
422 // Start us on the first stack.
423 if (getGameType() == GType_MAKINGOF)
424 changeToStack(kMakingOfStack, 1, 0, 0);
425 else if (getFeatures() & GF_DEMO)
426 changeToStack(kDemoStack, 2000, 0, 0);
427 else if (getFeatures() & GF_25TH)
428 changeToStack(kMenuStack, 1, 0, 0);
429 else
430 changeToStack(kIntroStack, 1, 0, 0);
431 }
432
433 while (!shouldQuit()) {
434 doFrame();
435 }
436
437 return Common::kNoError;
438 }
439
loadStackArchives(MystStack stackId)440 void MohawkEngine_Myst::loadStackArchives(MystStack stackId) {
441 for (uint i = 0; i < _mhk.size(); i++) {
442 delete _mhk[i];
443 }
444 _mhk.clear();
445
446 Common::String language;
447 if (getFeatures() & GF_LANGUAGE_FILES) {
448 language = getDatafileLanguageName("myst_");
449 }
450
451 if (!language.empty()) {
452 loadArchive(mystFiles[stackId], language.c_str(), false);
453 }
454
455 loadArchive(mystFiles[stackId], nullptr, true);
456
457 if (getFeatures() & GF_ME) {
458 if (!language.empty()) {
459 loadArchive("help", language.c_str(), false);
460 }
461
462 loadArchive("help", nullptr, true);
463 }
464
465 if (getFeatures() & GF_25TH) {
466 loadArchive("menu", nullptr, true);
467 }
468 }
469
loadArchive(const char * archiveName,const char * language,bool mandatory)470 void MohawkEngine_Myst::loadArchive(const char *archiveName, const char *language, bool mandatory) {
471 Common::String filename;
472 if (language) {
473 filename = Common::String::format("%s_%s.dat", archiveName, language);
474 } else {
475 filename = Common::String::format("%s.dat", archiveName);
476 }
477
478 Archive *archive = new MohawkArchive();
479 if (!archive->openFile(filename)) {
480 delete archive;
481 if (mandatory) {
482 error("Could not open %s", filename.c_str());
483 } else {
484 return;
485 }
486 }
487
488 _mhk.push_back(archive);
489 }
490
doFrame()491 void MohawkEngine_Myst::doFrame() {
492 // Update any background videos
493 _video->updateMovies();
494 if (isInteractive()) {
495 _waitingOnBlockingOperation = true;
496 _stack->runPersistentScripts();
497 _waitingOnBlockingOperation = false;
498 }
499
500 if (shouldPerformAutoSave(_lastSaveTime)) {
501 tryAutoSaving();
502 }
503
504 Common::Event event;
505 while (_system->getEventManager()->pollEvent(event)) {
506 switch (event.type) {
507 case Common::EVENT_MOUSEMOVE:
508 _mouseMoved = true;
509 break;
510 case Common::EVENT_LBUTTONUP:
511 _mouseClicked = false;
512 break;
513 case Common::EVENT_LBUTTONDOWN:
514 _mouseClicked = true;
515 break;
516 case Common::EVENT_KEYDOWN:
517 switch (event.kbd.keycode) {
518 case Common::KEYCODE_d:
519 if (event.kbd.flags & Common::KBD_CTRL) {
520 _console->attach();
521 _console->onFrame();
522 }
523 break;
524 case Common::KEYCODE_SPACE:
525 pauseGame();
526 break;
527 case Common::KEYCODE_F5:
528 runOptionsDialog();
529 break;
530 case Common::KEYCODE_ESCAPE:
531 if (_stack->getStackId() == kCreditsStack) {
532 // Don't allow going to the menu while the credits play
533 break;
534 }
535
536 if (!isInteractive()) {
537 // Try to skip the currently playing video
538 _escapePressed = true;
539 } else if (_stack->getStackId() == kMenuStack) {
540 // If the menu is active and a game is loaded, go back to the game
541 if (_prevStack) {
542 resumeFromMainMenu();
543 }
544 } else if (getFeatures() & GF_25TH) {
545 // If the game is interactive, open the main menu
546 goToMainMenu();
547 }
548 break;
549 case Common::KEYCODE_o:
550 if (event.kbd.flags & Common::KBD_CTRL) {
551 if (canLoadGameStateCurrently()) {
552 runLoadDialog();
553 }
554 }
555 break;
556 case Common::KEYCODE_s:
557 if (event.kbd.flags & Common::KBD_CTRL) {
558 if (canSaveGameStateCurrently()) {
559 runSaveDialog();
560 }
561 }
562 break;
563 default:
564 break;
565 }
566 break;
567 case Common::EVENT_KEYUP:
568 switch (event.kbd.keycode) {
569 case Common::KEYCODE_ESCAPE:
570 _escapePressed = false;
571 break;
572 default:
573 break;
574 }
575 break;
576 case Common::EVENT_QUIT:
577 case Common::EVENT_RTL:
578 // Attempt to autosave before exiting
579 tryAutoSaving();
580 break;
581 default:
582 break;
583 }
584 }
585
586 if (isInteractive()) {
587 Common::Point mousePos = _system->getEventManager()->getMousePos();
588
589 // Keep a reference to the card so it is not freed if a script switches to another card
590 MystCardPtr card = _card;
591 card->updateActiveResource(mousePos);
592 card->updateResourcesForInput(mousePos, _mouseClicked, _mouseMoved);
593
594 refreshCursor();
595
596 _mouseMoved = false;
597 }
598
599 _system->updateScreen();
600
601 // Cut down on CPU usage
602 _system->delayMillis(10);
603 }
604
runOptionsDialog()605 void MohawkEngine_Myst::runOptionsDialog() {
606 bool inMenu = (_stack->getStackId() == kMenuStack) && _prevStack;
607 bool actionsAllowed = inMenu || isInteractive();
608
609 MystScriptParserPtr stack;
610 if (inMenu) {
611 stack = _prevStack;
612 } else {
613 stack = _stack;
614 }
615
616 if (isGameStarted()) {
617 _optionsDialog->setZipMode(_gameState->_globals.zipMode);
618 _optionsDialog->setTransitions(_gameState->_globals.transitions);
619 } else {
620 _optionsDialog->setZipMode(ConfMan.getBool("zip_mode"));
621 _optionsDialog->setTransitions(ConfMan.getBool("transition_mode"));
622 }
623
624 _optionsDialog->setCanDropPage(actionsAllowed && _gameState->_globals.heldPage != kNoPage);
625 _optionsDialog->setCanShowMap(actionsAllowed && stack->getMap());
626 _optionsDialog->setCanReturnToMenu(actionsAllowed && stack->getStackId() != kDemoStack);
627
628 switch (runDialog(*_optionsDialog)) {
629 case MystOptionsDialog::kActionDropPage:
630 if (inMenu) {
631 resumeFromMainMenu();
632 }
633
634 dropPage();
635 break;
636 case MystOptionsDialog::kActionShowMap:
637 if (inMenu) {
638 resumeFromMainMenu();
639 }
640
641 stack->showMap();
642 break;
643 case MystOptionsDialog::kActionGoToMenu:
644 if (inMenu) {
645 resumeFromMainMenu();
646 }
647
648 changeToStack(kDemoStack, 2002, 0, 0);
649 break;
650 case MystOptionsDialog::kActionShowCredits:
651 if (isInteractive() && getGameType() != GType_MAKINGOF) {
652 _cursor->hideCursor();
653 changeToStack(kCreditsStack, 10000, 0, 0);
654 } else {
655 // Showing the credits in the middle of a script is not possible
656 // because it unloads the previous age, removing data needed by the
657 // rest of the script. Instead we just quit without showing the credits.
658 quitGame();
659 }
660 break;
661 case MystOptionsDialog::kActionSaveSettings:
662 if (isGameStarted()) {
663 _gameState->_globals.zipMode = _optionsDialog->getZipMode();
664 _gameState->_globals.transitions = _optionsDialog->getTransitions();
665 } else {
666 ConfMan.setBool("zip_mode", _optionsDialog->getZipMode());
667 ConfMan.setBool("transition_mode", _optionsDialog->getTransitions());
668 ConfMan.flushToDisk();
669 }
670 break;
671 default:
672 if (_optionsDialog->getLoadSlot() >= 0)
673 loadGameState(_optionsDialog->getLoadSlot());
674 if (_optionsDialog->getSaveSlot() >= 0)
675 saveGameState(_optionsDialog->getSaveSlot(), _optionsDialog->getSaveDescription());
676 break;
677 }
678 }
679
wait(uint32 duration,bool skippable)680 bool MohawkEngine_Myst::wait(uint32 duration, bool skippable) {
681 _waitingOnBlockingOperation = true;
682 uint32 end = getTotalPlayTime() + duration;
683
684 do {
685 doFrame();
686
687 if (_escapePressed && skippable) {
688 _escapePressed = false;
689 return true; // Return true if skipped
690 }
691 } while (getTotalPlayTime() < end && !shouldQuit());
692
693 _waitingOnBlockingOperation = false;
694 return false;
695 }
696
pauseEngineIntern(bool pause)697 void MohawkEngine_Myst::pauseEngineIntern(bool pause) {
698 MohawkEngine::pauseEngineIntern(pause);
699
700 if (pause) {
701 _video->pauseVideos();
702 } else {
703 _video->resumeVideos();
704
705 // We may have missed events while paused
706 _mouseClicked = (_eventMan->getButtonState() & 1) != 0;
707 }
708 }
709
changeToStack(MystStack stackId,uint16 card,uint16 linkSrcSound,uint16 linkDstSound)710 void MohawkEngine_Myst::changeToStack(MystStack stackId, uint16 card, uint16 linkSrcSound, uint16 linkDstSound) {
711 debug(2, "changeToStack(%d)", stackId);
712
713 // Fill screen with black and empty cursor
714 _cursor->setCursor(0);
715 _currentCursor = 0;
716
717 _sound->stopEffect();
718 _video->stopVideos();
719
720 // In Myst ME, play a fullscreen flyby movie, except when loading saves.
721 // Also play a flyby when first linking to Myst.
722 if (getFeatures() & GF_ME
723 && ((_stack && _stack->getStackId() == kMystStack) || (stackId == kMystStack && card == 4134))) {
724 playFlybyMovie(stackId);
725 }
726
727 _sound->stopBackground();
728
729 _gfx->clearScreen();
730
731 if (linkSrcSound)
732 playSoundBlocking(linkSrcSound);
733
734 if (_card) {
735 _card->leave();
736 _card.reset();
737 }
738
739 switch (stackId) {
740 case kChannelwoodStack:
741 _gameState->_globals.currentAge = kChannelwood;
742 _stack = MystScriptParserPtr(new MystStacks::Channelwood(this));
743 break;
744 case kCreditsStack:
745 _stack = MystScriptParserPtr(new MystStacks::Credits(this));
746 break;
747 case kDemoStack:
748 _gameState->_globals.currentAge = kSelenitic;
749 _stack = MystScriptParserPtr(new MystStacks::Demo(this));
750 break;
751 case kDniStack:
752 _gameState->_globals.currentAge = kDni;
753 _stack = MystScriptParserPtr(new MystStacks::Dni(this));
754 break;
755 case kIntroStack:
756 _stack = MystScriptParserPtr(new MystStacks::Intro(this));
757 break;
758 case kMakingOfStack:
759 _stack = MystScriptParserPtr(new MystStacks::MakingOf(this));
760 break;
761 case kMechanicalStack:
762 _gameState->_globals.currentAge = kMechanical;
763 _stack = MystScriptParserPtr(new MystStacks::Mechanical(this));
764 break;
765 case kMenuStack:
766 _stack = MystScriptParserPtr(new MystStacks::Menu(this));
767 break;
768 case kMystStack:
769 _gameState->_globals.currentAge = kMystLibrary;
770 _stack = MystScriptParserPtr(new MystStacks::Myst(this));
771 break;
772 case kDemoPreviewStack:
773 _stack = MystScriptParserPtr(new MystStacks::Preview(this));
774 break;
775 case kSeleniticStack:
776 _gameState->_globals.currentAge = kSelenitic;
777 _stack = MystScriptParserPtr(new MystStacks::Selenitic(this));
778 break;
779 case kDemoSlidesStack:
780 _gameState->_globals.currentAge = kStoneship;
781 _stack = MystScriptParserPtr(new MystStacks::Slides(this));
782 break;
783 case kStoneshipStack:
784 _gameState->_globals.currentAge = kStoneship;
785 _stack = MystScriptParserPtr(new MystStacks::Stoneship(this));
786 break;
787 default:
788 error("Unknown Myst stack %d", stackId);
789 }
790
791 loadStackArchives(stackId);
792
793 // Clear the resource cache and the image cache
794 _cache.clear();
795 _gfx->clearCache();
796
797 changeToCard(card, kTransitionCopy);
798
799 if (linkDstSound)
800 playSoundBlocking(linkDstSound);
801 }
802
changeToCard(uint16 card,TransitionType transition)803 void MohawkEngine_Myst::changeToCard(uint16 card, TransitionType transition) {
804 debug(2, "changeToCard(%d)", card);
805
806 _stack->disablePersistentScripts();
807
808 _video->stopVideos();
809
810 // Clear the resource cache and image cache
811 _cache.clear();
812 _gfx->clearCache();
813
814 _mouseClicked = false;
815 _mouseMoved = false;
816 _escapePressed = false;
817
818 if (_card) {
819 _card->leave();
820 }
821
822 _card = MystCardPtr(new MystCard(this, card));
823 _card->enter();
824
825 // The demo resets the cursor at each card change except when in the library
826 if (getFeatures() & GF_DEMO
827 && _gameState->_globals.currentAge != kMystLibrary) {
828 _cursor->setDefaultCursor();
829 }
830
831 // Make sure the screen is updated
832 if (transition != kNoTransition) {
833 if (_gameState->_globals.transitions) {
834 _gfx->runTransition(transition, Common::Rect(544, 333), 10, 0);
835 } else {
836 _gfx->copyBackBufferToScreen(Common::Rect(544, 333));
837 }
838 }
839
840 // Debug: Show resource rects
841 if (_showResourceRects)
842 _card->drawResourceRects();
843 }
844
setMainCursor(uint16 cursor)845 void MohawkEngine_Myst::setMainCursor(uint16 cursor) {
846 _currentCursor = _mainCursor = cursor;
847 _cursor->setCursor(_currentCursor);
848 }
849
refreshCursor()850 void MohawkEngine_Myst::refreshCursor() {
851 int16 cursor = _card->getActiveResourceCursor();
852 if (cursor == -1) {
853 cursor = _mainCursor;
854 }
855
856 if (cursor != _currentCursor) {
857 _currentCursor = cursor;
858 _cursor->setCursor(cursor);
859 }
860 }
861
redrawResource(MystAreaImageSwitch * resource,bool update)862 void MohawkEngine_Myst::redrawResource(MystAreaImageSwitch *resource, bool update) {
863 resource->drawConditionalDataToScreen(_stack->getVar(resource->getImageSwitchVar()), update);
864 }
865
loadResource(Common::SeekableReadStream * rlstStream,MystArea * parent)866 MystArea *MohawkEngine_Myst::loadResource(Common::SeekableReadStream *rlstStream, MystArea *parent) {
867 MystArea *resource = nullptr;
868 ResourceType type = static_cast<ResourceType>(rlstStream->readUint16LE());
869
870 debugC(kDebugResource, "\tType: %d", type);
871 debugC(kDebugResource, "\tSub_Record: %d", (parent == nullptr) ? 0 : 1);
872
873 switch (type) {
874 case kMystAreaAction:
875 resource = new MystAreaAction(this, type, rlstStream, parent);
876 break;
877 case kMystAreaVideo:
878 resource = new MystAreaVideo(this, type, rlstStream, parent);
879 break;
880 case kMystAreaActionSwitch:
881 resource = new MystAreaActionSwitch(this, type, rlstStream, parent);
882 break;
883 case kMystAreaImageSwitch:
884 resource = new MystAreaImageSwitch(this, type, rlstStream, parent);
885 break;
886 case kMystAreaSlider:
887 resource = new MystAreaSlider(this, type, rlstStream, parent);
888 break;
889 case kMystAreaDrag:
890 resource = new MystAreaDrag(this, type, rlstStream, parent);
891 break;
892 case kMystVideoInfo:
893 resource = new MystVideoInfo(this, type, rlstStream, parent);
894 break;
895 case kMystAreaHover:
896 resource = new MystAreaHover(this, type, rlstStream, parent);
897 break;
898 default:
899 resource = new MystArea(this, type, rlstStream, parent);
900 break;
901 }
902
903 return resource;
904 }
905
loadGameState(int slot)906 Common::Error MohawkEngine_Myst::loadGameState(int slot) {
907 tryAutoSaving();
908
909 if (_gameState->load(slot))
910 return Common::kNoError;
911
912 return Common::kUnknownError;
913 }
914
saveGameState(int slot,const Common::String & desc)915 Common::Error MohawkEngine_Myst::saveGameState(int slot, const Common::String &desc) {
916 const Graphics::Surface *thumbnail = nullptr;
917 if (_stack->getStackId() == kMenuStack) {
918 thumbnail = _gfx->getThumbnailForMainMenu();
919 }
920
921 return _gameState->save(slot, desc, thumbnail, false) ? Common::kNoError : Common::kUnknownError;
922 }
923
tryAutoSaving()924 void MohawkEngine_Myst::tryAutoSaving() {
925 if (!canSaveGameStateCurrently()) {
926 return; // Can't save right now, try again on the next frame
927 }
928
929 _lastSaveTime = _system->getMillis();
930
931 if (!_gameState->isAutoSaveAllowed()) {
932 return; // Can't autosave ever, try again after the next autosave delay
933 }
934
935 const Graphics::Surface *thumbnail = nullptr;
936 if (_stack->getStackId() == kMenuStack) {
937 thumbnail = _gfx->getThumbnailForMainMenu();
938 }
939
940 if (!_gameState->save(MystGameState::kAutoSaveSlot, "Autosave", thumbnail, true))
941 warning("Attempt to autosave has failed.");
942 }
943
hasGameSaveSupport() const944 bool MohawkEngine_Myst::hasGameSaveSupport() const {
945 return !(getFeatures() & GF_DEMO) && getGameType() != GType_MAKINGOF;
946 }
947
isInteractive()948 bool MohawkEngine_Myst::isInteractive() {
949 return !_stack->isScriptRunning() && !_waitingOnBlockingOperation;
950 }
951
canLoadGameStateCurrently()952 bool MohawkEngine_Myst::canLoadGameStateCurrently() {
953 bool isInMenu = (_stack->getStackId() == kMenuStack) && _prevStack;
954
955 if (!isInMenu) {
956 if (!isInteractive()) {
957 return false;
958 }
959
960 if (_card->isDraggingResource()) {
961 return false;
962 }
963 }
964
965 if (!hasGameSaveSupport()) {
966 // No loading in the demo/makingof
967 return false;
968 }
969
970 return true;
971 }
972
canSaveGameStateCurrently()973 bool MohawkEngine_Myst::canSaveGameStateCurrently() {
974 if (!canLoadGameStateCurrently()) {
975 return false;
976 }
977
978 // There's a limited number of stacks the game can save in
979 switch (_stack->getStackId()) {
980 case kChannelwoodStack:
981 case kDniStack:
982 case kMechanicalStack:
983 case kMystStack:
984 case kSeleniticStack:
985 case kStoneshipStack:
986 return true;
987 case kMenuStack:
988 return _prevStack;
989 default:
990 return false;
991 }
992 }
993
runLoadDialog()994 void MohawkEngine_Myst::runLoadDialog() {
995 GUI::SaveLoadChooser slc(_("Load game:"), _("Load"), false);
996
997 pauseEngine(true);
998 int slot = slc.runModalWithCurrentTarget();
999 pauseEngine(false);
1000
1001 if (slot >= 0) {
1002 loadGameState(slot);
1003 }
1004 }
1005
runSaveDialog()1006 void MohawkEngine_Myst::runSaveDialog() {
1007 GUI::SaveLoadChooser slc(_("Save game:"), _("Save"), true);
1008
1009 pauseEngine(true);
1010 int slot = slc.runModalWithCurrentTarget();
1011 pauseEngine(false);
1012
1013 if (slot >= 0) {
1014 Common::String result(slc.getResultString());
1015 if (result.empty()) {
1016 // If the user was lazy and entered no save name, come up with a default name.
1017 result = slc.createDefaultSaveDescription(slot);
1018 }
1019
1020 saveGameState(slot, result);
1021 }
1022 }
1023
dropPage()1024 void MohawkEngine_Myst::dropPage() {
1025 HeldPage page = _gameState->_globals.heldPage;
1026 bool whitePage = page == kWhitePage;
1027 bool bluePage = page - 1 < 6;
1028 bool redPage = page - 7 < 6;
1029
1030 // Play drop page sound
1031 _sound->playEffect(800);
1032
1033 // Drop page
1034 _gameState->_globals.heldPage = kNoPage;
1035
1036 // Redraw page area
1037 if (whitePage && _gameState->_globals.currentAge == kMystLibrary) {
1038 _stack->toggleVar(41);
1039 _card->redrawArea(41);
1040 } else if (bluePage) {
1041 if (page == kBlueFirePlacePage) {
1042 if (_gameState->_globals.currentAge == kMystLibrary)
1043 _card->redrawArea(24);
1044 } else {
1045 _card->redrawArea(103);
1046 }
1047 } else if (redPage) {
1048 if (page == kRedFirePlacePage) {
1049 if (_gameState->_globals.currentAge == kMystLibrary)
1050 _card->redrawArea(25);
1051 } else if (page == kRedStoneshipPage) {
1052 if (_gameState->_globals.currentAge == kStoneship)
1053 _card->redrawArea(35);
1054 } else {
1055 _card->redrawArea(102);
1056 }
1057 }
1058
1059 setMainCursor(kDefaultMystCursor);
1060 refreshCursor();
1061 }
1062
readSoundBlock(Common::ReadStream * stream) const1063 MystSoundBlock MohawkEngine_Myst::readSoundBlock(Common::ReadStream *stream) const {
1064 MystSoundBlock soundBlock;
1065 soundBlock.sound = stream->readSint16LE();
1066 debugCN(kDebugView, "Sound Control: %d = ", soundBlock.sound);
1067
1068 if (soundBlock.sound > 0) {
1069 debugC(kDebugView, "Play new Sound, change volume");
1070 debugC(kDebugView, "\tSound: %d", soundBlock.sound);
1071 soundBlock.soundVolume = stream->readUint16LE();
1072 debugC(kDebugView, "\tVolume: %d", soundBlock.soundVolume);
1073 } else if (soundBlock.sound == kMystSoundActionContinue) {
1074 debugC(kDebugView, "Continue current sound");
1075 } else if (soundBlock.sound == kMystSoundActionChangeVolume) {
1076 debugC(kDebugView, "Continue current sound, change volume");
1077 soundBlock.soundVolume = stream->readUint16LE();
1078 debugC(kDebugView, "\tVolume: %d", soundBlock.soundVolume);
1079 } else if (soundBlock.sound == kMystSoundActionStop) {
1080 debugC(kDebugView, "Stop sound");
1081 } else if (soundBlock.sound == kMystSoundActionConditional) {
1082 debugC(kDebugView, "Conditional sound list");
1083 soundBlock.soundVar = stream->readUint16LE();
1084 debugC(kDebugView, "\tVar: %d", soundBlock.soundVar);
1085 uint16 soundCount = stream->readUint16LE();
1086 debugC(kDebugView, "\tCount: %d", soundCount);
1087
1088 for (uint16 i = 0; i < soundCount; i++) {
1089 MystSoundBlock::SoundItem sound;
1090
1091 sound.action = stream->readSint16LE();
1092 debugC(kDebugView, "\t\tCondition %d: Action %d", i, sound.action);
1093 if (sound.action == kMystSoundActionChangeVolume || sound.action >= 0) {
1094 sound.volume = stream->readUint16LE();
1095 debugC(kDebugView, "\t\tCondition %d: Volume %d", i, sound.volume);
1096 }
1097
1098 soundBlock.soundList.push_back(sound);
1099 }
1100 } else {
1101 error("Unknown sound control value '%d' in card '%d'", soundBlock.sound, _card->getId());
1102 }
1103
1104 return soundBlock;
1105 }
1106
applySoundBlock(const MystSoundBlock & block)1107 void MohawkEngine_Myst::applySoundBlock(const MystSoundBlock &block) {
1108 int16 soundAction = 0;
1109 uint16 soundActionVolume = 0;
1110
1111 if (block.sound == kMystSoundActionConditional) {
1112 uint16 soundVarValue = _stack->getVar(block.soundVar);
1113 if (soundVarValue >= block.soundList.size())
1114 warning("Conditional sound variable outside range");
1115 else {
1116 soundAction = block.soundList[soundVarValue].action;
1117 soundActionVolume = block.soundList[soundVarValue].volume;
1118 }
1119 } else {
1120 soundAction = block.sound;
1121 soundActionVolume = block.soundVolume;
1122 }
1123
1124 if (soundAction == kMystSoundActionContinue)
1125 debug(2, "Continuing with current sound");
1126 else if (soundAction == kMystSoundActionChangeVolume) {
1127 debug(2, "Continuing with current sound, changing volume");
1128 _sound->changeBackgroundVolume(soundActionVolume);
1129 } else if (soundAction == kMystSoundActionStop) {
1130 debug(2, "Stopping sound");
1131 _sound->stopBackground();
1132 } else if (soundAction > 0) {
1133 debug(2, "Playing new sound %d", soundAction);
1134 _sound->playBackground(soundAction, soundActionVolume);
1135 } else {
1136 error("Unknown sound action %d", soundAction);
1137 }
1138 }
1139
goToMainMenu()1140 void MohawkEngine_Myst::goToMainMenu() {
1141 _waitingOnBlockingOperation = false;
1142
1143 _prevCard = _card;
1144 _prevStack = _stack;
1145 _gfx->saveStateForMainMenu();
1146
1147 MystStacks::Menu *menu = new MystStacks::Menu(this);
1148 menu->setInGame(true);
1149 menu->setCanSave(canSaveGameStateCurrently());
1150
1151 _stack = MystScriptParserPtr(menu);
1152 _card.reset();
1153
1154 // Clear the resource cache and the image cache
1155 _cache.clear();
1156 _gfx->clearCache();
1157
1158 _card = MystCardPtr(new MystCard(this, 1000));
1159 _card->enter();
1160
1161 _gfx->copyBackBufferToScreen(Common::Rect(544, 333));
1162 }
1163
isGameStarted() const1164 bool MohawkEngine_Myst::isGameStarted() const {
1165 return _prevStack || (_stack->getStackId() != kMenuStack);
1166 }
1167
resumeFromMainMenu()1168 void MohawkEngine_Myst::resumeFromMainMenu() {
1169 _card->leave();
1170 _card.reset();
1171
1172 _stack = _prevStack;
1173 _prevStack.reset();
1174
1175
1176 // Clear the resource cache and image cache
1177 _cache.clear();
1178 _gfx->clearCache();
1179
1180 _mouseClicked = false;
1181 _mouseMoved = false;
1182 _escapePressed = false;
1183 _card = _prevCard;
1184
1185 _prevCard.reset();
1186 }
1187
1188 } // End of namespace Mohawk
1189