From 73b3a43b893e78fd7f18eae490e24f253414ce31 Mon Sep 17 00:00:00 2001 From: Bastien Bouclet Date: Sun, 29 Apr 2018 19:22:50 +0200 Subject: MOHAWK: MYST: Introduce a main menu stack Used in the 25th Anniversary edition of Myst ME --- engines/mohawk/POTFILES | 1 + engines/mohawk/detection_tables.h | 66 +++++++ engines/mohawk/dialogs.cpp | 9 +- engines/mohawk/dialogs.h | 3 +- engines/mohawk/module.mk | 1 + engines/mohawk/mohawk.h | 9 +- engines/mohawk/myst.cpp | 154 ++++++++++++--- engines/mohawk/myst.h | 16 +- engines/mohawk/myst_graphics.cpp | 87 ++++++++- engines/mohawk/myst_graphics.h | 18 ++ engines/mohawk/myst_stacks/intro.cpp | 9 +- engines/mohawk/myst_stacks/menu.cpp | 366 +++++++++++++++++++++++++++++++++++ engines/mohawk/myst_stacks/menu.h | 99 ++++++++++ engines/mohawk/myst_state.cpp | 21 +- engines/mohawk/myst_state.h | 5 +- 15 files changed, 823 insertions(+), 41 deletions(-) create mode 100644 engines/mohawk/myst_stacks/menu.cpp create mode 100644 engines/mohawk/myst_stacks/menu.h (limited to 'engines') diff --git a/engines/mohawk/POTFILES b/engines/mohawk/POTFILES index 3ed2309dc5..2f21a754fa 100644 --- a/engines/mohawk/POTFILES +++ b/engines/mohawk/POTFILES @@ -2,6 +2,7 @@ engines/mohawk/detection.cpp engines/mohawk/dialogs.cpp engines/mohawk/mohawk.cpp engines/mohawk/myst.cpp +engines/mohawk/myst_stacks/menu.cpp engines/mohawk/riven.cpp engines/mohawk/riven_stack.cpp engines/mohawk/riven_stacks/aspit.cpp diff --git a/engines/mohawk/detection_tables.h b/engines/mohawk/detection_tables.h index f44d612bcd..0acfaf49ff 100644 --- a/engines/mohawk/detection_tables.h +++ b/engines/mohawk/detection_tables.h @@ -338,6 +338,72 @@ static const MohawkGameDescription gameDescriptions[] = { 0, }, + // Myst Masterpiece Edition - 25th Anniversary + // English Windows + // Created by the ScummVM team + { + { + "myst", + "Masterpiece Edition - 25th Anniversary", + { + {"MYST.DAT", 0, "c4cae9f143b5947262e6cb2397e1617e", -1}, + {"MENU.DAT", 0, "7dc23051084f79b1c2bccc84cdec0503", -1}, + AD_LISTEND + }, + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST_ME + }, + GType_MYST, + GF_ME | GF_25TH, + 0, + }, + + // Myst Masterpiece Edition - 25th Anniversary + // French Windows + // Created by the ScummVM team + { + { + "myst", + "Masterpiece Edition - 25th Anniversary", + { + {"MYST.DAT", 0, "aea81633b2d2ae498f09072fb87263b6", -1}, + {"MENU.DAT", 0, "7dc23051084f79b1c2bccc84cdec0503", -1}, + AD_LISTEND + }, + Common::FR_FRA, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST_ME + }, + GType_MYST, + GF_ME | GF_25TH, + 0, + }, + + // Myst Masterpiece Edition - 25th Anniversary + // German Windows + // Created by the ScummVM team + { + { + "myst", + "Masterpiece Edition", + { + {"MYST.DAT", 0, "f88e0ace66dbca78eebdaaa1d3314ceb", -1}, + {"MENU.DAT", 0, "7dc23051084f79b1c2bccc84cdec0503", -1}, + AD_LISTEND + }, + Common::DE_DEU, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST_ME + }, + GType_MYST, + GF_ME, + 0, + }, + // Riven: The Sequel to Myst // Version 1.0 (5CD) // From clone2727 diff --git a/engines/mohawk/dialogs.cpp b/engines/mohawk/dialogs.cpp index a83116ed07..252ad2d9d4 100644 --- a/engines/mohawk/dialogs.cpp +++ b/engines/mohawk/dialogs.cpp @@ -98,7 +98,7 @@ MohawkOptionsDialog::MohawkOptionsDialog(MohawkEngine *vm) : _vm(vm), _loadSlot(-1), _saveSlot(-1) { _loadButton = new GUI::ButtonWidget(this, 245, 25, 100, 25, _("~L~oad"), nullptr, kLoadCmd); _saveButton = new GUI::ButtonWidget(this, 245, 60, 100, 25, _("~S~ave"), nullptr, kSaveCmd); - new GUI::ButtonWidget(this, 245, 95, 100, 25, _("~Q~uit"), nullptr, kQuitCmd); + _quitButton = new GUI::ButtonWidget(this, 245, 95, 100, 25, _("~Q~uit"), nullptr, kQuitCmd); new GUI::ButtonWidget(this, 95, 160, 120, 25, _("~O~K"), nullptr, GUI::kOKCmd); new GUI::ButtonWidget(this, 225, 160, 120, 25, _("~C~ancel"), nullptr, GUI::kCloseCmd); @@ -228,6 +228,13 @@ void MystOptionsDialog::open() { _zipModeCheckbox->setState(_vm->_gameState->_globals.zipMode); _transitionsCheckbox->setState(_vm->_gameState->_globals.transitions); + + if (_vm->getFeatures() & GF_25TH) { + // The 25th anniversary version has a main menu, no need to show these buttons here + _loadButton->setVisible(false); + _saveButton->setVisible(false); + _quitButton->setVisible(false); + } } void MystOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) { diff --git a/engines/mohawk/dialogs.h b/engines/mohawk/dialogs.h index 1de60d314b..567a0fc214 100644 --- a/engines/mohawk/dialogs.h +++ b/engines/mohawk/dialogs.h @@ -87,11 +87,12 @@ public: int getSaveSlot() const { return _saveSlot; } Common::String getSaveDescription() const { return _saveDescription; } -private: +protected: MohawkEngine *_vm; GUI::ButtonWidget *_loadButton; GUI::ButtonWidget *_saveButton; + GUI::ButtonWidget *_quitButton; GUI::SaveLoadChooser *_loadDialog; GUI::SaveLoadChooser *_saveDialog; diff --git a/engines/mohawk/module.mk b/engines/mohawk/module.mk index 4957ff3c2b..e76b1aefaf 100644 --- a/engines/mohawk/module.mk +++ b/engines/mohawk/module.mk @@ -45,6 +45,7 @@ MODULE_OBJS += \ myst_stacks/intro.o \ myst_stacks/makingof.o \ myst_stacks/mechanical.o \ + myst_stacks/menu.o \ myst_stacks/myst.o \ myst_stacks/preview.o \ myst_stacks/selenitic.o \ diff --git a/engines/mohawk/mohawk.h b/engines/mohawk/mohawk.h index 8184f46bad..3a50a2a14d 100644 --- a/engines/mohawk/mohawk.h +++ b/engines/mohawk/mohawk.h @@ -58,10 +58,11 @@ enum MohawkGameType { }; enum MohawkGameFeatures { - GF_ME = (1 << 0), // Myst Masterpiece Edition - GF_DVD = (1 << 1), - GF_DEMO = (1 << 2), - GF_LB_10 = (1 << 3) // very early Living Books 1.0 games + GF_ME = (1 << 0), // Myst Masterpiece Edition + GF_25TH = (1 << 1), // Myst Masterpiece Edition - 25th Anniversary + GF_DVD = (1 << 2), + GF_DEMO = (1 << 3), + GF_LB_10 = (1 << 4) // very early Living Books 1.0 games }; struct MohawkGameDescription; diff --git a/engines/mohawk/myst.cpp b/engines/mohawk/myst.cpp index f7c0e9bf01..c936046a16 100644 --- a/engines/mohawk/myst.cpp +++ b/engines/mohawk/myst.cpp @@ -49,6 +49,7 @@ #include "mohawk/myst_stacks/intro.h" #include "mohawk/myst_stacks/makingof.h" #include "mohawk/myst_stacks/mechanical.h" +#include "mohawk/myst_stacks/menu.h" #include "mohawk/myst_stacks/myst.h" #include "mohawk/myst_stacks/preview.h" #include "mohawk/myst_stacks/selenitic.h" @@ -163,7 +164,8 @@ static const char *mystFiles[] = { "selen.dat", "slides.dat", "sneak.dat", - "stone.dat" + "stone.dat", + "menu.dat" }; // Myst Hardcoded Movie Paths @@ -345,6 +347,11 @@ Common::Error MohawkEngine_Myst::run() { // Cursor is visible by default _cursor->showCursor(); + _mhk.resize(3); + _mhk[0] = new MohawkArchive(); + _mhk[1] = new MohawkArchive(); + _mhk[2] = new MohawkArchive(); + // Load game from launcher/command line if requested if (ConfMan.hasKey("save_slot") && hasGameSaveSupport()) { int saveSlot = ConfMan.getInt("save_slot"); @@ -356,16 +363,20 @@ Common::Error MohawkEngine_Myst::run() { changeToStack(kMakingOfStack, 1, 0, 0); else if (getFeatures() & GF_DEMO) changeToStack(kDemoStack, 2000, 0, 0); + else if (getFeatures() & GF_25TH) + changeToStack(kMenuStack, 1, 0, 0); else changeToStack(kIntroStack, 1, 0, 0); } // Load Help System (Masterpiece Edition Only) if (getFeatures() & GF_ME) { - MohawkArchive *mhk = new MohawkArchive(); - if (!mhk->openFile("help.dat")) + if (!_mhk[1]->openFile("help.dat")) error("Could not load help.dat"); - _mhk.push_back(mhk); + } + if (getFeatures() & GF_25TH) { + if (!_mhk[2]->openFile("menu.dat")) + error("Could not load menu.dat"); } while (!shouldQuit()) { @@ -415,7 +426,23 @@ void MohawkEngine_Myst::doFrame() { runOptionsDialog(); break; case Common::KEYCODE_ESCAPE: - _escapePressed = true; + if (_stack->getStackId() == kCreditsStack) { + // Don't allow going to the menu while the credits play + break; + } + + if (!isInteractive()) { + // Try to skip the currently playing video + _escapePressed = true; + } else if (_stack->getStackId() == kMenuStack) { + // If the menu is active and a game is loaded, go back to the game + if (_prevStack) { + resumeFromMainMenu(); + } + } else if (getFeatures() & GF_25TH) { + // If the game is interactive, open the main menu + goToMainMenu(); + } break; case Common::KEYCODE_o: if (event.kbd.flags & Common::KBD_CTRL) { @@ -474,18 +501,40 @@ void MohawkEngine_Myst::doFrame() { } void MohawkEngine_Myst::runOptionsDialog() { - _optionsDialog->setCanDropPage(isInteractive() && _gameState->_globals.heldPage != kNoPage); - _optionsDialog->setCanShowMap(isInteractive() && _stack->getMap()); - _optionsDialog->setCanReturnToMenu(isInteractive() && _stack->getStackId() != kDemoStack); + bool inMenu = (_stack->getStackId() == kMenuStack) && _prevStack; + bool actionsAllowed = inMenu || isInteractive(); + + MystScriptParserPtr stack; + if (inMenu) { + stack = _prevStack; + } else { + stack = _stack; + } + + _optionsDialog->setCanDropPage(actionsAllowed && _gameState->_globals.heldPage != kNoPage); + _optionsDialog->setCanShowMap(actionsAllowed && stack->getMap()); + _optionsDialog->setCanReturnToMenu(actionsAllowed && stack->getStackId() != kDemoStack); switch (runDialog(*_optionsDialog)) { case MystOptionsDialog::kActionDropPage: + if (inMenu) { + resumeFromMainMenu(); + } + dropPage(); break; case MystOptionsDialog::kActionShowMap: - _stack->showMap(); + if (inMenu) { + resumeFromMainMenu(); + } + + stack->showMap(); break; case MystOptionsDialog::kActionGoToMenu: + if (inMenu) { + resumeFromMainMenu(); + } + changeToStack(kDemoStack, 2002, 0, 0); break; case MystOptionsDialog::kActionShowCredits: @@ -596,6 +645,9 @@ void MohawkEngine_Myst::changeToStack(MystStack stackId, uint16 card, uint16 lin _gameState->_globals.currentAge = kMechanical; _stack = MystScriptParserPtr(new MystStacks::Mechanical(this)); break; + case kMenuStack: + _stack = MystScriptParserPtr(new MystStacks::Menu(this)); + break; case kMystStack: _gameState->_globals.currentAge = kMystLibrary; _stack = MystScriptParserPtr(new MystStacks::Myst(this)); @@ -621,12 +673,8 @@ void MohawkEngine_Myst::changeToStack(MystStack stackId, uint16 card, uint16 lin // If the array is empty, add a new one. Otherwise, delete the first // entry which is the stack file (the second, if there, is the help file). - if (_mhk.empty()) - _mhk.push_back(new MohawkArchive()); - else { - delete _mhk[0]; - _mhk[0] = new MohawkArchive(); - } + delete _mhk[0]; + _mhk[0] = new MohawkArchive(); if (!_mhk[0]->openFile(mystFiles[stackId])) error("Could not open %s", mystFiles[stackId]); @@ -745,6 +793,8 @@ MystArea *MohawkEngine_Myst::loadResource(Common::SeekableReadStream *rlstStream } Common::Error MohawkEngine_Myst::loadGameState(int slot) { + tryAutoSaving(); + if (_gameState->load(slot)) return Common::kNoError; @@ -752,7 +802,12 @@ Common::Error MohawkEngine_Myst::loadGameState(int slot) { } Common::Error MohawkEngine_Myst::saveGameState(int slot, const Common::String &desc) { - return _gameState->save(slot, desc, false) ? Common::kNoError : Common::kUnknownError; + const Graphics::Surface *thumbnail = nullptr; + if (_stack->getStackId() == kMenuStack) { + thumbnail = _gfx->getThumbnailForMainMenu(); + } + + return _gameState->save(slot, desc, thumbnail, false) ? Common::kNoError : Common::kUnknownError; } void MohawkEngine_Myst::tryAutoSaving() { @@ -766,7 +821,12 @@ void MohawkEngine_Myst::tryAutoSaving() { return; // Can't autosave ever, try again after the next autosave delay } - if (!_gameState->save(MystGameState::kAutoSaveSlot, "Autosave", true)) + const Graphics::Surface *thumbnail = nullptr; + if (_stack->getStackId() == kMenuStack) { + thumbnail = _gfx->getThumbnailForMainMenu(); + } + + if (!_gameState->save(MystGameState::kAutoSaveSlot, "Autosave", thumbnail, true)) warning("Attempt to autosave has failed."); } @@ -779,12 +839,16 @@ bool MohawkEngine_Myst::isInteractive() { } bool MohawkEngine_Myst::canLoadGameStateCurrently() { - if (!isInteractive()) { - return false; - } + bool isInMenu = (_stack->getStackId() == kMenuStack) && _prevStack; - if (_card->isDraggingResource()) { - return false; + if (!isInMenu) { + if (!isInteractive()) { + return false; + } + + if (_card->isDraggingResource()) { + return false; + } } if (!hasGameSaveSupport()) { @@ -809,6 +873,8 @@ bool MohawkEngine_Myst::canSaveGameStateCurrently() { case kSeleniticStack: case kStoneshipStack: return true; + case kMenuStack: + return _prevStack; default: return false; } @@ -960,4 +1026,48 @@ void MohawkEngine_Myst::applySoundBlock(const MystSoundBlock &block) { } } +void MohawkEngine_Myst::goToMainMenu() { + _waitingOnBlockingOperation = false; + + _prevCard = _card; + _prevStack = _stack; + _gfx->saveStateForMainMenu(); + + MystStacks::Menu *menu = new MystStacks::Menu(this); + menu->setInGame(true); + menu->setCanSave(canSaveGameStateCurrently()); + + _stack = MystScriptParserPtr(menu); + _card.reset(); + + // Clear the resource cache and the image cache + _cache.clear(); + _gfx->clearCache(); + + _card = MystCardPtr(new MystCard(this, 1000)); + _card->enter(); + + _gfx->copyBackBufferToScreen(Common::Rect(544, 333)); +} + +void MohawkEngine_Myst::resumeFromMainMenu() { + _card->leave(); + _card.reset(); + + _stack = _prevStack; + _prevStack.reset(); + + + // Clear the resource cache and image cache + _cache.clear(); + _gfx->clearCache(); + + _mouseClicked = false; + _mouseMoved = false; + _escapePressed = false; + _card = _prevCard; + + _prevCard.reset(); +} + } // End of namespace Mohawk diff --git a/engines/mohawk/myst.h b/engines/mohawk/myst.h index 43c918a7e2..96a3c22931 100644 --- a/engines/mohawk/myst.h +++ b/engines/mohawk/myst.h @@ -75,7 +75,8 @@ enum MystStack { kSeleniticStack, // Selenitic Age kDemoSlidesStack, // Demo Slideshow kDemoPreviewStack, // Demo Myst Library Preview - kStoneshipStack // Stoneship Age + kStoneshipStack, // Stoneship Age + kMenuStack // Main menu }; // Transitions @@ -190,20 +191,27 @@ public: void tryAutoSaving(); bool hasFeature(EngineFeature f) const override; + void resumeFromMainMenu(); + + void runLoadDialog(); + void runSaveDialog(); + void runOptionsDialog(); + private: MystConsole *_console; MystOptionsDialog *_optionsDialog; ResourceCache _cache; + MystScriptParserPtr _prevStack; + MystCardPtr _card; + MystCardPtr _prevCard; uint32 _lastSaveTime; bool hasGameSaveSupport() const; void pauseEngineIntern(bool pause) override; - void runLoadDialog(); - void runSaveDialog(); - void runOptionsDialog(); + void goToMainMenu(); void dropPage(); diff --git a/engines/mohawk/myst_graphics.cpp b/engines/mohawk/myst_graphics.cpp index 6b43ea02bc..75649275f8 100644 --- a/engines/mohawk/myst_graphics.cpp +++ b/engines/mohawk/myst_graphics.cpp @@ -28,12 +28,18 @@ #include "common/system.h" #include "common/textconsole.h" #include "engines/util.h" +#include "graphics/fonts/ttf.h" +#include "graphics/fontman.h" #include "graphics/palette.h" +#include "graphics/scaler.h" #include "image/pict.h" namespace Mohawk { -MystGraphics::MystGraphics(MohawkEngine_Myst* vm) : GraphicsManager(), _vm(vm) { +MystGraphics::MystGraphics(MohawkEngine_Myst* vm) : + GraphicsManager(), + _vm(vm), + _menuFont(nullptr) { _bmpDecoder = new MystBitmap(); _viewport = Common::Rect(544, 332); @@ -55,6 +61,24 @@ MystGraphics::MystGraphics(MohawkEngine_Myst* vm) : GraphicsManager(), _vm(vm) { // Initialize our buffer _backBuffer = new Graphics::Surface(); _backBuffer->create(_vm->_system->getWidth(), _vm->_system->getHeight(), _pixelFormat); + + _mainMenuBackupScreen.reset(new Graphics::Surface()); + _mainMenuBackupScreenThumbnail.reset(new Graphics::Surface()); + _mainMenuBackupBackBuffer.reset(new Graphics::Surface()); + + if (_vm->getFeatures() & GF_25TH) { + const char *menuFontName = "NotoSans-ExtraBold.ttf"; +#ifdef USE_FREETYPE2 + Common::SeekableReadStream *fontStream = SearchMan.createReadStreamForMember(menuFontName); + if (fontStream) { + _menuFont = Graphics::loadTTFFont(*fontStream, 16); + delete fontStream; + } else +#endif + { + warning("Unable to open the menu font file '%s'", menuFontName); + } + } } MystGraphics::~MystGraphics() { @@ -62,6 +86,7 @@ MystGraphics::~MystGraphics() { _backBuffer->free(); delete _backBuffer; + delete _menuFont; } MohawkSurface *MystGraphics::decodeImage(uint16 id) { @@ -799,4 +824,64 @@ void MystGraphics::setPaletteToScreen() { _vm->_system->getPaletteManager()->setPalette(_palette, 0, 256); } +void MystGraphics::saveStateForMainMenu() { + Graphics::Surface *screen = _vm->_system->lockScreen(); + _mainMenuBackupScreen->copyFrom(*screen); + _vm->_system->unlockScreen(); + + // Create a thumbnail of the screen that will be used when saving from the main menu + createThumbnailFromScreen(_mainMenuBackupScreenThumbnail.get()); + + _mainMenuBackupBackBuffer->copyFrom(*_backBuffer); +} + +void MystGraphics::restoreStateForMainMenu() { + _vm->_system->copyRectToScreen(_mainMenuBackupScreen->getPixels(), _mainMenuBackupScreen->pitch, + 0, 0, _mainMenuBackupScreen->w, _mainMenuBackupScreen->h); + + _backBuffer->copyFrom(*_mainMenuBackupBackBuffer); + + _mainMenuBackupScreen->free(); + _mainMenuBackupScreenThumbnail->free(); + _mainMenuBackupBackBuffer->free(); +} + +Graphics::Surface *MystGraphics::getThumbnailForMainMenu() const { + return _mainMenuBackupScreenThumbnail.get(); +} + +void MystGraphics::drawText(uint16 image, const char *text, const Common::Rect &dest, uint8 r, uint8 g, uint8 b, Graphics::TextAlign align, int16 deltaY) { + MohawkSurface *mhkSurface = findImage(image); + Graphics::Surface *surface = mhkSurface->getSurface(); + + const Graphics::Font *font = getMenuFont(); + font->drawString(surface, text, dest.left, dest.top + deltaY, dest.width(), surface->format.RGBToColor(r, g, b), align, 0, false); +} + +Common::Rect MystGraphics::getTextBoundingBox(const char *text, const Common::Rect &dest, Graphics::TextAlign align) { + const Graphics::Font *font = getMenuFont(); + return font->getBoundingBox(text, dest.left, dest.top, dest.width(), align); +} + +const Graphics::Font *MystGraphics::getMenuFont() const { + const Graphics::Font *font; + if (_menuFont) { + font = _menuFont; + } else { + font = FontMan.getFontByUsage(Graphics::FontManager::kBigGUIFont); + } + return font; +} + +void MystGraphics::replaceImageWithRect(uint16 destImage, uint16 sourceImage, const Common::Rect &sourceRect) { + MohawkSurface *sourceSurface = findImage(sourceImage); + const Graphics::Surface sourceArea = sourceSurface->getSurface()->getSubArea(sourceRect); + + Graphics::Surface *replacementSurface = new Graphics::Surface(); + replacementSurface->copyFrom(sourceArea); + + MohawkSurface *destSurface = new MohawkSurface(replacementSurface, nullptr, 0, 0); + addImageToCache(destImage, destSurface); +} + } // End of namespace Mohawk diff --git a/engines/mohawk/myst_graphics.h b/engines/mohawk/myst_graphics.h index b8217f6cfc..c9fd8a7865 100644 --- a/engines/mohawk/myst_graphics.h +++ b/engines/mohawk/myst_graphics.h @@ -26,6 +26,7 @@ #include "mohawk/graphics.h" #include "common/file.h" +#include "graphics/font.h" namespace Mohawk { @@ -58,6 +59,15 @@ public: void setPaletteToScreen(); const byte *getPalette() const { return _palette; } + void saveStateForMainMenu(); + void restoreStateForMainMenu(); + Graphics::Surface *getThumbnailForMainMenu() const; + + Common::Rect getTextBoundingBox(const char *text, const Common::Rect &dest, Graphics::TextAlign align); + void drawText(uint16 image, const char *text, const Common::Rect &dest, uint8 r, uint8 g, uint8 b, Graphics::TextAlign align, int16 deltaY); + + void replaceImageWithRect(uint16 destImage, uint16 sourceImage, const Common::Rect &sourceRect); + protected: MohawkSurface *decodeImage(uint16 id) override; MohawkEngine *getVM() override { return (MohawkEngine *)_vm; } @@ -71,6 +81,10 @@ private: Common::Rect _viewport; byte _palette[256 * 3]; + Common::ScopedPtr _mainMenuBackupScreen; + Common::ScopedPtr _mainMenuBackupScreenThumbnail; + Common::ScopedPtr _mainMenuBackupBackBuffer; + void transitionDissolve(Common::Rect rect, uint step); void transitionSlideToLeft(Common::Rect rect, uint16 steps, uint16 delay); void transitionSlideToRight(Common::Rect rect, uint16 steps, uint16 delay); @@ -83,6 +97,10 @@ private: byte getColorIndex(const byte *palette, byte red, byte green, byte blue); void applyImagePatches(uint16 id, const MohawkSurface *mhkSurface) const; + + Graphics::Font *_menuFont; + + const Graphics::Font *getMenuFont() const; }; } // End of namespace Mohawk diff --git a/engines/mohawk/myst_stacks/intro.cpp b/engines/mohawk/myst_stacks/intro.cpp index f6a7987b25..57937c1bcc 100644 --- a/engines/mohawk/myst_stacks/intro.cpp +++ b/engines/mohawk/myst_stacks/intro.cpp @@ -137,7 +137,14 @@ void Intro::introMovies_run() { void Intro::o_playIntroMovies(uint16 var, const ArgumentsArray &args) { _introMoviesRunning = true; - _introStep = 0; + + if (_vm->getFeatures() & GF_25TH) { + // In the 25th anniversary version, the Broderbund / Cyan Logo were already shown + // before the main menu. No need to play them again here. + _introStep = 4; + } else { + _introStep = 0; + } } void Intro::mystLinkBook_run() { diff --git a/engines/mohawk/myst_stacks/menu.cpp b/engines/mohawk/myst_stacks/menu.cpp new file mode 100644 index 0000000000..ebcfb15a65 --- /dev/null +++ b/engines/mohawk/myst_stacks/menu.cpp @@ -0,0 +1,366 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "mohawk/myst.h" +#include "mohawk/myst_areas.h" +#include "mohawk/myst_card.h" +#include "mohawk/myst_graphics.h" +#include "mohawk/myst_state.h" +#include "mohawk/cursors.h" +#include "mohawk/sound.h" +#include "mohawk/video.h" +#include "mohawk/myst_stacks/menu.h" + +#include "common/translation.h" +#include "graphics/cursorman.h" +#include "gui/message.h" + +namespace Mohawk { +namespace MystStacks { + +Menu::Menu(MohawkEngine_Myst *vm) : + MystScriptParser(vm, kMenuStack), + _inGame(false), + _canSave(false), + _wasCursorVisible(true), + _introMoviesRunning(false) { + + for (uint i = 0; i < ARRAYSIZE(_menuItemHovered); i++) { + _menuItemHovered[i] = false; + } + + setupOpcodes(); +} + +Menu::~Menu() { +} + +void Menu::setupOpcodes() { + // "Stack-Specific" Opcodes + REGISTER_OPCODE(150, Menu, o_menuItemEnter); + REGISTER_OPCODE(151, Menu, o_menuItemLeave); + REGISTER_OPCODE(152, Menu, o_menuResume); + REGISTER_OPCODE(153, Menu, o_menuLoad); + REGISTER_OPCODE(154, Menu, o_menuSave); + REGISTER_OPCODE(155, Menu, o_menuNew); + REGISTER_OPCODE(156, Menu, o_menuOptions); + REGISTER_OPCODE(157, Menu, o_menuQuit); + + // "Init" Opcodes + REGISTER_OPCODE(200, Menu, o_playIntroMovies); + REGISTER_OPCODE(201, Menu, o_menuInit); + + // "Exit" Opcodes + REGISTER_OPCODE(300, Menu, NOP); + REGISTER_OPCODE(301, Menu, o_menuExit); +} + +void Menu::disablePersistentScripts() { + _introMoviesRunning = false; +} + +void Menu::runPersistentScripts() { + if (_introMoviesRunning) + introMovies_run(); +} + +uint16 Menu::getVar(uint16 var) { + switch (var) { + case 1000: // New game + case 1001: // Load + case 1004: // Quit + case 1005: // Options + return _menuItemHovered[var - 1000] ? 1 : 0; + case 1002: // Save + if (_canSave) { + return _menuItemHovered[var - 1000] ? 1 : 0; + } else { + return 2; + } + case 1003: // Resume + if (_inGame) { + return _menuItemHovered[var - 1000] ? 1 : 0; + } else { + return 2; + } + default: + return MystScriptParser::getVar(var); + } +} + +void Menu::o_menuInit(uint16 var, const ArgumentsArray &args) { + _vm->pauseEngine(true); + + if (_inGame) { + _wasCursorVisible = CursorMan.isVisible(); + } + + if (!_wasCursorVisible) { + CursorMan.showMouse(true); + } + + struct MenuButton { + uint16 highlightedIndex; + uint16 disabledIndex; + Graphics::TextAlign align; + }; + + static const MenuButton buttons[] = { + { 1, 0, Graphics::kTextAlignRight }, + { 1, 0, Graphics::kTextAlignRight }, + { 1, 2, Graphics::kTextAlignRight }, + { 1, 2, Graphics::kTextAlignRight }, + { 1, 0, Graphics::kTextAlignRight }, + { 1, 0, Graphics::kTextAlignLeft } + }; + + const char **buttonCaptions = getButtonCaptions(); + + for (uint i = 0; i < ARRAYSIZE(buttons); i++) { + MystAreaImageSwitch *image = _vm->getCard()->getResource(2 * i + 0); + MystAreaHover *hover = _vm->getCard()->getResource (2 * i + 1); + + drawButtonImages(buttonCaptions[i], image, buttons[i].align, buttons[i].highlightedIndex, buttons[i].disabledIndex); + hover->setRect(image->getRect()); + } +} + +const char **Menu::getButtonCaptions() const { + static const char *buttonCaptionsEnglish[] = { + "NEW GAME", + "LOAD GAME", + "SAVE GAME", + "RESUME", + "QUIT", + "OPTIONS" + }; + + static const char *buttonCaptionsFrench[] = { + "NOUVEAU", + "CHARGER", + "SAUVER", + "REPRENDRE", + "QUITTER", + "OPTIONS" + }; + + static const char *buttonCaptionsGerman[] = { + "NEUES SPIEL", + "SPIEL LADEN", + "SPIEL SPEICHERN", + "FORTSETZEN", + "BEENDEN", + "OPTIONEN" + }; + + switch (_vm->getLanguage()) { + case Common::FR_FRA: + return buttonCaptionsFrench; + case Common::DE_DEU: + return buttonCaptionsGerman; + case Common::EN_ANY: + default: + return buttonCaptionsEnglish; + } +} + +void Menu::drawButtonImages(const char *text, MystAreaImageSwitch *area, Graphics::TextAlign align, uint16 highlightedIndex, uint16 disabledIndex) const { + Common::Rect backgroundRect = area->getRect(); + Common::Rect textBoundingBox = _vm->_gfx->getTextBoundingBox(text, backgroundRect, align); + + // Restrict the rectangle to the portion were the text will be drawn + if (align == Graphics::kTextAlignLeft) { + backgroundRect.right = textBoundingBox.right; + } else if (align == Graphics::kTextAlignRight) { + backgroundRect.left = textBoundingBox.left; + } else { + error("Unexpected align: %d", align); + } + + // Update the area with the new background rect + area->setRect(backgroundRect); + + MystAreaImageSwitch::SubImage idle = area->getSubImage(0); + area->setSubImageRect(0, Common::Rect(backgroundRect.left, idle.rect.top, backgroundRect.right, idle.rect.bottom)); + + // Align the text to the top of the destination rectangles + int16 deltaY = backgroundRect.top - textBoundingBox.top; + + if (highlightedIndex) { + replaceButtonSubImageWithText(text, align, area, highlightedIndex, backgroundRect, deltaY, 215, 216, 219); + } + + if (disabledIndex) { + replaceButtonSubImageWithText(text, align, area, disabledIndex, backgroundRect, deltaY, 136, 140, 145); + } + + uint16 cardBackground = _vm->getCard()->getBackgroundImageId(); + _vm->_gfx->drawText(cardBackground, text, backgroundRect, 181, 184, 189, align, deltaY); +} + +void Menu::replaceButtonSubImageWithText(const char *text, const Graphics::TextAlign &align, MystAreaImageSwitch *area, + uint16 subimageIndex, const Common::Rect &backgroundRect, int16 deltaY, + uint8 r, uint8 g, uint8 b) const { + uint16 cardBackground = _vm->getCard()->getBackgroundImageId(); + + MystAreaImageSwitch::SubImage highlighted = area->getSubImage(subimageIndex); + Common::Rect subImageRect(0, 0, backgroundRect.width(), backgroundRect.height()); + + // Create an image exactly the size of the rendered text with the backdrop as a background + _vm->_gfx->replaceImageWithRect(highlighted.wdib, cardBackground, backgroundRect); + area->setSubImageRect(subimageIndex, subImageRect); + + // Draw the text in the subimage + _vm->_gfx->drawText(highlighted.wdib, text, subImageRect, r, g, b, align, deltaY); +} + +void Menu::o_menuItemEnter(uint16 var, const ArgumentsArray &args) { + _menuItemHovered[var - 1000] = true; + _vm->getCard()->redrawArea(var); +} + +void Menu::o_menuItemLeave(uint16 var, const ArgumentsArray &args) { + _menuItemHovered[var - 1000] = false; + _vm->getCard()->redrawArea(var); +} + +void Menu::o_menuResume(uint16 var, const ArgumentsArray &args) { + if (!_inGame) { + return; + } + + _vm->resumeFromMainMenu(); +} + +void Menu::o_menuLoad(uint16 var, const ArgumentsArray &args) { + if (!showConfirmationDialog(_("Are you sure you want to load a saved game? All unsaved progress will be lost."), + _("Load game"), _("Cancel"))) { + return; + } + + _vm->runLoadDialog(); +} + +void Menu::o_menuSave(uint16 var, const ArgumentsArray &args) { + if (!_canSave) { + return; + } + + _vm->runSaveDialog(); +} + +void Menu::o_menuNew(uint16 var, const ArgumentsArray &args) { + if (!showConfirmationDialog(_("Are you sure you want to start a new game? All unsaved progress will be lost."), + _("New game"), _("Cancel"))) { + return; + } + + _vm->_gameState->reset(); + _vm->setTotalPlayTime(0); + _vm->setMainCursor(kDefaultMystCursor); + _vm->changeToStack(kIntroStack, 1, 0, 0); +} + +void Menu::o_menuOptions(uint16 var, const ArgumentsArray &args) { + resetButtons(); + + _vm->runOptionsDialog(); +} + +void Menu::o_menuQuit(uint16 var, const ArgumentsArray &args) { + if (!showConfirmationDialog(_("Are you sure you want to quit? All unsaved progress will be lost."), _("Quit"), + _("Cancel"))) { + return; + } + + _vm->changeToStack(kCreditsStack, 10000, 0, 0); +} + +void Menu::o_menuExit(uint16 var, const ArgumentsArray &args) { + if (_inGame) { + _vm->_gfx->restoreStateForMainMenu(); + } + + CursorMan.showMouse(_wasCursorVisible); + + _vm->pauseEngine(false); +} + +void Menu::o_playIntroMovies(uint16 var, const ArgumentsArray &args) { + _introMoviesRunning = true; + _introStep = 0; +} + +void Menu::introMovies_run() { + // Play Intro Movies + // This is all quite messy... + + VideoEntryPtr video; + + switch (_introStep) { + case 0: + _introStep = 1; + video = _vm->playMovie("broder", kIntroStack); + video->center(); + break; + case 1: + if (!_vm->_video->isVideoPlaying()) + _introStep = 2; + break; + case 2: + _introStep = 3; + video = _vm->playMovie("cyanlogo", kIntroStack); + video->center(); + break; + case 3: + if (!_vm->_video->isVideoPlaying()) + _introStep = 4; + break; + default: + _vm->changeToCard(1000, kTransitionCopy); + } +} + +bool Menu::showConfirmationDialog(const char *message, const char *confirmButton, const char *cancelButton) { + if (!_inGame) { + return true; + } + + resetButtons(); + + GUI::MessageDialog dialog(message, confirmButton, cancelButton); + + return dialog.runModal() !=0; +} + +void Menu::resetButtons() { + for (uint i = 0; i < ARRAYSIZE(_menuItemHovered); i++) { + _menuItemHovered[i] = false; + _vm->getCard()->redrawArea(1000 + i); + } + + _vm->doFrame(); +} + + +} // End of namespace MystStacks +} // End of namespace Mohawk diff --git a/engines/mohawk/myst_stacks/menu.h b/engines/mohawk/myst_stacks/menu.h new file mode 100644 index 0000000000..01dd597485 --- /dev/null +++ b/engines/mohawk/myst_stacks/menu.h @@ -0,0 +1,99 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef MYST_SCRIPTS_MENU_H +#define MYST_SCRIPTS_MENU_H + +#include "mohawk/myst_scripts.h" + +#include "common/scummsys.h" +#include "common/util.h" + +#include "graphics/font.h" + +namespace Mohawk { + +class MystAreaVideo; +struct MystScriptEntry; + +namespace MystStacks { + +#define DECLARE_OPCODE(x) void x(uint16 var, const ArgumentsArray &args) + +class Menu : public MystScriptParser { +public: + explicit Menu(MohawkEngine_Myst *vm); + ~Menu() override; + + void setInGame(bool inGame) { + _inGame = inGame; + } + + void setCanSave(bool canSave) { + _canSave = canSave; + } + + void disablePersistentScripts() override; + void runPersistentScripts() override; + +private: + void setupOpcodes(); + uint16 getVar(uint16 var) override; + + DECLARE_OPCODE(o_playIntroMovies); + DECLARE_OPCODE(o_menuItemEnter); + DECLARE_OPCODE(o_menuItemLeave); + DECLARE_OPCODE(o_menuResume); + DECLARE_OPCODE(o_menuLoad); + DECLARE_OPCODE(o_menuSave); + DECLARE_OPCODE(o_menuNew); + DECLARE_OPCODE(o_menuOptions); + DECLARE_OPCODE(o_menuQuit); + DECLARE_OPCODE(o_menuInit); + DECLARE_OPCODE(o_menuExit); + + bool _inGame; + bool _canSave; + bool _menuItemHovered[6]; + bool _wasCursorVisible; + + bool _introMoviesRunning; + int _introStep; + void introMovies_run(); + + bool showConfirmationDialog(const char *message, const char *confirmButton, const char *cancelButton); + + void drawButtonImages(const char *text, MystAreaImageSwitch *area, Graphics::TextAlign align, uint16 highlightedIndex, uint16 disabledIndex) const; + void replaceButtonSubImageWithText(const char *text, const Graphics::TextAlign &align, MystAreaImageSwitch *area, + uint16 subimageIndex, const Common::Rect &backgroundRect, int16 deltaY, + uint8 r, uint8 g, uint8 b) const; + const char **getButtonCaptions() const; + void resetButtons(); + +}; + +} // End of namespace MystStacks +} // End of namespace Mohawk + +#undef DECLARE_OPCODE + +#endif diff --git a/engines/mohawk/myst_state.cpp b/engines/mohawk/myst_state.cpp index a0df54f8a1..85e81da607 100644 --- a/engines/mohawk/myst_state.cpp +++ b/engines/mohawk/myst_state.cpp @@ -65,7 +65,14 @@ bool MystSaveMetadata::sync(Common::Serializer &s) { const int MystGameState::kAutoSaveSlot = 0; -MystGameState::MystGameState(MohawkEngine_Myst *vm, Common::SaveFileManager *saveFileMan) : _vm(vm), _saveFileMan(saveFileMan) { +MystGameState::MystGameState(MohawkEngine_Myst *vm, Common::SaveFileManager *saveFileMan) : + _vm(vm), + _saveFileMan(saveFileMan) { + + reset(); +} + +void MystGameState::reset() { // Most of the variables are zero at game start. memset(&_globals, 0, sizeof(_globals)); memset(&_myst, 0, sizeof(_myst)); @@ -184,14 +191,14 @@ void MystGameState::loadMetadata(int slot) { delete metadataFile; } -bool MystGameState::save(int slot, const Common::String &desc, bool autoSave) { +bool MystGameState::save(int slot, const Common::String &desc, const Graphics::Surface *thumbnail, bool autoSave) { if (!saveState(slot)) { return false; } updateMetadateForSaving(desc, autoSave); - return saveMetadata(slot); + return saveMetadata(slot, thumbnail); } bool MystGameState::saveState(int slot) { @@ -234,7 +241,7 @@ void MystGameState::updateMetadateForSaving(const Common::String &desc, bool aut _metadata.autoSave = autoSave; } -bool MystGameState::saveMetadata(int slot) { +bool MystGameState::saveMetadata(int slot, const Graphics::Surface *thumbnail) { // Write the metadata to a separate file so that the save files // are still compatible with the original engine Common::String metadataFilename = buildMetadataFilename(slot); @@ -248,7 +255,11 @@ bool MystGameState::saveMetadata(int slot) { _metadata.sync(m); // Append a thumbnail - Graphics::saveThumbnail(*metadataFile); + if (thumbnail) { + Graphics::saveThumbnail(*metadataFile, *thumbnail); + } else { + Graphics::saveThumbnail(*metadataFile); + } metadataFile->finalize(); delete metadataFile; diff --git a/engines/mohawk/myst_state.h b/engines/mohawk/myst_state.h index 7dc75c74f4..a3bb38b808 100644 --- a/engines/mohawk/myst_state.h +++ b/engines/mohawk/myst_state.h @@ -108,8 +108,9 @@ public: static SaveStateDescriptor querySaveMetaInfos(int slot); static Common::String querySaveDescription(int slot); + void reset(); bool load(int slot); - bool save(int slot, const Common::String &desc, bool autoSave); + bool save(int slot, const Common::String &desc, const Graphics::Surface *thumbnail, bool autosave); bool isAutoSaveAllowed(); static void deleteSave(int slot); @@ -346,7 +347,7 @@ private: void loadMetadata(int slot); bool saveState(int slot); void updateMetadateForSaving(const Common::String &desc, bool autoSave); - bool saveMetadata(int slot); + bool saveMetadata(int slot, const Graphics::Surface *thumbnail); // The values in these regions are lists of VIEW resources // which correspond to visited zip destinations -- cgit v1.2.3