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