diff options
Diffstat (limited to 'engines/mohawk')
56 files changed, 1984 insertions, 895 deletions
diff --git a/engines/mohawk/POTFILES b/engines/mohawk/POTFILES index 54d9dcaa3a..42d1d084c2 100644 --- a/engines/mohawk/POTFILES +++ b/engines/mohawk/POTFILES @@ -1,3 +1,6 @@ +engines/mohawk/detection.cpp engines/mohawk/dialogs.cpp engines/mohawk/myst.cpp engines/mohawk/riven.cpp +engines/mohawk/riven_external.cpp +engines/mohawk/mohawk.cpp diff --git a/engines/mohawk/bitmap.cpp b/engines/mohawk/bitmap.cpp index 6435daf46f..d8c6d6aacd 100644 --- a/engines/mohawk/bitmap.cpp +++ b/engines/mohawk/bitmap.cpp @@ -53,6 +53,16 @@ MohawkBitmap::MohawkBitmap() { _drawTable = drawTable; _drawTableSize = ARRAYSIZE(drawTable); + + _header.width = 0; + _header.height = 0; + _header.bytesPerRow = 0; + _header.format = 0; + _header.colorTable.colorCount = 0; + _header.colorTable.palette = nullptr; + _header.colorTable.rgbBits = 0; + _header.colorTable.tableSize = 0; + _data = nullptr; } MohawkBitmap::~MohawkBitmap() { diff --git a/engines/mohawk/configure.engine b/engines/mohawk/configure.engine index 47402c4560..ccb9499ef0 100644 --- a/engines/mohawk/configure.engine +++ b/engines/mohawk/configure.engine @@ -1,6 +1,6 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine mohawk "Mohawk" yes "cstime myst riven" "Living Books" +add_engine mohawk "Mohawk" yes "cstime myst riven" "Living Books" "highres" add_engine cstime "Where in Time is Carmen Sandiego?" no add_engine riven "Riven: The Sequel to Myst" no "" "" "16bit" -add_engine myst "Myst" no +add_engine myst "Myst" yes diff --git a/engines/mohawk/console.cpp b/engines/mohawk/console.cpp index fd79e53b07..f08eee9677 100644 --- a/engines/mohawk/console.cpp +++ b/engines/mohawk/console.cpp @@ -42,6 +42,7 @@ #ifdef ENABLE_RIVEN #include "mohawk/riven.h" #include "mohawk/riven_external.h" +#include "mohawk/riven_sound.h" #endif namespace Mohawk { diff --git a/engines/mohawk/cstime.cpp b/engines/mohawk/cstime.cpp index 3b26378819..b2889be714 100644 --- a/engines/mohawk/cstime.cpp +++ b/engines/mohawk/cstime.cpp @@ -54,6 +54,7 @@ MohawkEngine_CSTime::MohawkEngine_CSTime(OSystem *syst, const MohawkGameDescript _console = 0; _gfx = 0; + _sound = 0; _cursor = 0; _interface = 0; _view = 0; @@ -66,6 +67,7 @@ MohawkEngine_CSTime::~MohawkEngine_CSTime() { delete _interface; delete _view; delete _console; + delete _sound; delete _gfx; delete _rnd; } @@ -75,6 +77,7 @@ Common::Error MohawkEngine_CSTime::run() { _console = new CSTimeConsole(this); _gfx = new CSTimeGraphics(this); + _sound = new Sound(this); _cursor = new DefaultCursorManager(this, ID_CURS); _interface = new CSTimeInterface(this); diff --git a/engines/mohawk/cstime.h b/engines/mohawk/cstime.h index f95222d3a1..393032aaa9 100644 --- a/engines/mohawk/cstime.h +++ b/engines/mohawk/cstime.h @@ -111,7 +111,7 @@ enum { }; struct CSTimeEvent { - CSTimeEvent() { } + CSTimeEvent() : type(0), param1(0), param2(0) { } CSTimeEvent(uint16 t, uint16 p1, uint16 p2) : type(t), param1(p1), param2(p2) { } uint16 type; @@ -136,6 +136,7 @@ public: Common::RandomSource *_rnd; + Sound *_sound; CSTimeGraphics *_gfx; bool _needsUpdate; diff --git a/engines/mohawk/cstime_game.cpp b/engines/mohawk/cstime_game.cpp index 8eced701c3..c939d8bc24 100644 --- a/engines/mohawk/cstime_game.cpp +++ b/engines/mohawk/cstime_game.cpp @@ -94,6 +94,11 @@ CSTimeChar::CSTimeChar(MohawkEngine_CSTime *vm, CSTimeScene *scene, uint id) : _ _lastTime2 = 0; _lastTime3 = 0; + _unknown1 = _unknown2 = _unknown3 = 0; + _enabled = false; + _nextCue = 0; + _waveStatus = 0; + _playingWaveId = 0; } diff --git a/engines/mohawk/cstime_ui.cpp b/engines/mohawk/cstime_ui.cpp index f3fe27a966..59be95adf6 100644 --- a/engines/mohawk/cstime_ui.cpp +++ b/engines/mohawk/cstime_ui.cpp @@ -79,6 +79,8 @@ CSTimeInterface::CSTimeInterface(MohawkEngine_CSTime *vm) : _vm(vm) { _rolloverTextFeature = NULL; _bubbleTextFeature = NULL; + _draggedItem = 0; + _mouseWasInScene = false; _state = kCSTimeInterfaceStateNormal; @@ -1034,6 +1036,8 @@ CSTimeInventoryDisplay::CSTimeInventoryDisplay(MohawkEngine_CSTime *vm, Common:: _cuffsState = false; _cuffsShape = 10; + _draggedItem = 0; + _invRect = baseRect; for (uint i = 0; i < MAX_DISPLAYED_ITEMS; i++) { diff --git a/engines/mohawk/cstime_view.cpp b/engines/mohawk/cstime_view.cpp index 7879175bb0..8727560094 100644 --- a/engines/mohawk/cstime_view.cpp +++ b/engines/mohawk/cstime_view.cpp @@ -243,7 +243,7 @@ void CSTimeModule::defaultMoveProc(Feature *feature) { if ((feature->_flags & kFeatureNewDisable) || (feature->_flags & kFeatureNewDisableOnReset)) { feature->_data.enabled = 0; } - feature->_dirty = 1; + feature->_dirty = true; if (feature->_flags & kFeatureInternalRegion) { // TODO: create region [+140] (if not already done) } @@ -257,7 +257,7 @@ void CSTimeModule::defaultMoveProc(Feature *feature) { // TODO: or clip with bounds } } - feature->_dirty = 1; + feature->_dirty = true; if (feature->_flags & kFeatureNewInternalTiming) { feature->_nextTime += feature->_delayTime; } else { @@ -277,7 +277,7 @@ void CSTimeModule::defaultMoveProc(Feature *feature) { } feature->_data.currOffset = 26; - feature->_done = 0; + feature->_done = false; } if (feature->_flags & kFeatureNewDisable) feature->_data.enabled = 0; @@ -307,7 +307,7 @@ void CSTimeModule::defaultMoveProc(Feature *feature) { } case 0: // TODO: set ptr +176 to 1 - feature->_done = 1; + feature->_done = true; if (feature->_doneProc) { (this->*(feature->_doneProc))(feature); // TODO: with -1 } diff --git a/engines/mohawk/cursors.cpp b/engines/mohawk/cursors.cpp index 4b66829e6a..b78f71a71d 100644 --- a/engines/mohawk/cursors.cpp +++ b/engines/mohawk/cursors.cpp @@ -34,8 +34,8 @@ #include "graphics/wincursor.h" #ifdef ENABLE_MYST -#include "mohawk/bitmap.h" #include "mohawk/myst.h" +#include "mohawk/myst_graphics.h" #endif namespace Mohawk { @@ -86,11 +86,9 @@ void DefaultCursorManager::setCursor(uint16 id) { #ifdef ENABLE_MYST MystCursorManager::MystCursorManager(MohawkEngine_Myst *vm) : _vm(vm) { - _bmpDecoder = new MystBitmap(); } MystCursorManager::~MystCursorManager() { - delete _bmpDecoder; } void MystCursorManager::showCursor() { @@ -111,17 +109,27 @@ void MystCursorManager::setCursor(uint16 id) { return; } - // Both Myst and Myst ME use the "MystBitmap" format for cursor images. - MohawkSurface *mhkSurface = _bmpDecoder->decodeImage(_vm->getResource(ID_WDIB, id)); - Graphics::Surface *surface = mhkSurface->getSurface(); Common::SeekableReadStream *clrcStream = _vm->getResource(ID_CLRC, id); uint16 hotspotX = clrcStream->readUint16LE(); uint16 hotspotY = clrcStream->readUint16LE(); delete clrcStream; + // Both Myst and Myst ME use the "MystBitmap" format for cursor images. + MohawkSurface *mhkSurface = _vm->_gfx->findImage(id); + Graphics::Surface *surface = mhkSurface->getSurface(); + // Myst ME stores some cursors as 24bpp images instead of 8bpp if (surface->format.bytesPerPixel == 1) { - CursorMan.replaceCursor(surface->getPixels(), surface->w, surface->h, hotspotX, hotspotY, 0); + // The transparent color is almost always 255, except for the main cursor (100) + // in the D'ni archive, where it is 0. + // Using the color of the first pixel as the transparent color for the main cursor always works. + byte transparentColor; + if (id == kDefaultMystCursor) { + transparentColor = ((byte *)surface->getPixels())[0]; + } else { + transparentColor = 255; + } + CursorMan.replaceCursor(surface->getPixels(), surface->w, surface->h, hotspotX, hotspotY, transparentColor); // We're using the screen palette for the original game, but we need // to use this for any 8bpp cursor in ME. @@ -133,7 +141,6 @@ void MystCursorManager::setCursor(uint16 id) { } _vm->_needsUpdate = true; - delete mhkSurface; } void MystCursorManager::setDefaultCursor() { diff --git a/engines/mohawk/cursors.h b/engines/mohawk/cursors.h index c41b5c273e..742ae30107 100644 --- a/engines/mohawk/cursors.h +++ b/engines/mohawk/cursors.h @@ -102,7 +102,6 @@ enum { }; class MohawkEngine_Myst; -class MystBitmap; // The cursor manager for Myst // Uses WDIB + CLRC resources @@ -119,7 +118,6 @@ public: private: MohawkEngine_Myst *_vm; - MystBitmap *_bmpDecoder; }; #endif // ENABLE_MYST diff --git a/engines/mohawk/detection.cpp b/engines/mohawk/detection.cpp index 986b35c85e..439ea152c4 100644 --- a/engines/mohawk/detection.cpp +++ b/engines/mohawk/detection.cpp @@ -26,6 +26,7 @@ #include "common/savefile.h" #include "common/system.h" #include "common/textconsole.h" +#include "common/translation.h" #include "mohawk/livingbooks.h" @@ -40,6 +41,7 @@ #ifdef ENABLE_RIVEN #include "mohawk/riven.h" +#include "mohawk/riven_saveload.h" #endif namespace Mohawk { @@ -53,7 +55,7 @@ struct MohawkGameDescription { }; const char* MohawkEngine::getGameId() const { - return _gameDescription->desc.gameid; + return _gameDescription->desc.gameId; } uint32 MohawkEngine::getFeatures() const { @@ -112,7 +114,7 @@ bool MohawkEngine_Riven::hasFeature(EngineFeature f) const { static const PlainGameDescriptor mohawkGames[] = { {"mohawk", "Mohawk Game"}, {"myst", "Myst"}, - {"MakingOfMyst", "The Making of Myst"}, + {"makingofmyst", "The Making of Myst"}, {"riven", "Riven: The Sequel to Myst"}, {"zoombini", "Logical Journey of the Zoombinis"}, {"cstime", "Where in Time is Carmen Sandiego?"}, @@ -160,10 +162,24 @@ static const char *directoryGlobs[] = { 0 }; +static const ADExtraGuiOptionsMap optionsList[] = { + { + GAMEOPTION_PLAY_MYST_FLYBY, + { + _s("Play the Myst fly by movie"), + _s("The Myst fly by movie was not played by the original engine."), + "playmystflyby", + false + } + }, + + AD_EXTRA_GUI_OPTIONS_TERMINATOR +}; + class MohawkMetaEngine : public AdvancedMetaEngine { public: - MohawkMetaEngine() : AdvancedMetaEngine(Mohawk::gameDescriptions, sizeof(Mohawk::MohawkGameDescription), mohawkGames) { - _singleid = "mohawk"; + MohawkMetaEngine() : AdvancedMetaEngine(Mohawk::gameDescriptions, sizeof(Mohawk::MohawkGameDescription), mohawkGames, optionsList) { + _singleId = "mohawk"; _maxScanDepth = 2; _directoryGlobs = directoryGlobs; } @@ -183,6 +199,7 @@ public: virtual bool hasFeature(MetaEngineFeature f) const; virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; virtual SaveStateList listSaves(const char *target) const; + SaveStateList listSavesForPrefix(const char *prefix, const char *extension) const; virtual int getMaximumSaveSlot() const { return 999; } virtual void removeSaveState(const char *target, int slot) const; virtual SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; @@ -199,53 +216,86 @@ bool MohawkMetaEngine::hasFeature(MetaEngineFeature f) const { || (f == kSavesSupportPlayTime); } +SaveStateList MohawkMetaEngine::listSavesForPrefix(const char *prefix, const char *extension) const { + Common::String pattern = Common::String::format("%s-###.%s", prefix, extension); + Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles(pattern); + size_t prefixLen = strlen(prefix); + + SaveStateList saveList; + for (Common::StringArray::const_iterator filename = filenames.begin(); filename != filenames.end(); ++filename) { + // Extract the slot number from the filename + char slot[4]; + slot[0] = (*filename)[prefixLen + 1]; + slot[1] = (*filename)[prefixLen + 2]; + slot[2] = (*filename)[prefixLen + 3]; + slot[3] = '\0'; + + int slotNum = atoi(slot); + + saveList.push_back(SaveStateDescriptor(slotNum, "")); + } + + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); + + return saveList; +} + SaveStateList MohawkMetaEngine::listSaves(const char *target) const { - Common::StringArray filenames; SaveStateList saveList; // Loading games is only supported in Myst/Riven currently. #ifdef ENABLE_MYST if (strstr(target, "myst")) { - filenames = Mohawk::MystGameState::generateSaveGameList(); + saveList = listSavesForPrefix("myst", "mys"); - for (uint32 i = 0; i < filenames.size(); i++) - saveList.push_back(SaveStateDescriptor(i, filenames[i])); - } else + for (SaveStateList::iterator save = saveList.begin(); save != saveList.end(); ++save) { + // Read the description from the save + int slot = save->getSaveSlot(); + Common::String description = Mohawk::MystGameState::querySaveDescription(slot); + save->setDescription(description); + } + } #endif +#ifdef ENABLE_RIVEN if (strstr(target, "riven")) { - filenames = g_system->getSavefileManager()->listSavefiles("*.rvn"); + saveList = listSavesForPrefix("riven", "rvn"); - for (uint32 i = 0; i < filenames.size(); i++) - saveList.push_back(SaveStateDescriptor(i, filenames[i])); + for (SaveStateList::iterator save = saveList.begin(); save != saveList.end(); ++save) { + // Read the description from the save + int slot = save->getSaveSlot(); + Common::String description = Mohawk::RivenSaveLoad::querySaveDescription(slot); + save->setDescription(description); + } } +#endif return saveList; } void MohawkMetaEngine::removeSaveState(const char *target, int slot) const { + // Removing saved games is only supported in Myst/Riven currently. #ifdef ENABLE_MYST if (strstr(target, "myst")) { - Common::StringArray filenames = Mohawk::MystGameState::generateSaveGameList(); - Mohawk::MystGameState::deleteSave(filenames[slot]); - } else + Mohawk::MystGameState::deleteSave(slot); + } #endif +#ifdef ENABLE_RIVEN if (strstr(target, "riven")) { - Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles("*.rvn"); - g_system->getSavefileManager()->removeSavefile(filenames[slot].c_str()); + Mohawk::RivenSaveLoad::deleteSave(slot); } +#endif } SaveStateDescriptor MohawkMetaEngine::querySaveMetaInfos(const char *target, int slot) const { #ifdef ENABLE_MYST if (strstr(target, "myst")) { - Common::StringArray filenames = Mohawk::MystGameState::generateSaveGameList(); - - if (slot >= (int) filenames.size()) { - return SaveStateDescriptor(); - } - - return Mohawk::MystGameState::querySaveMetaInfos(filenames[slot]); + return Mohawk::MystGameState::querySaveMetaInfos(slot); + } +#endif +#ifdef ENABLE_RIVEN + if (strstr(target, "riven")) { + return Mohawk::RivenSaveLoad::querySaveMetaInfos(slot); } else #endif { diff --git a/engines/mohawk/detection_tables.h b/engines/mohawk/detection_tables.h index 97d2932d57..9cc52a78b3 100644 --- a/engines/mohawk/detection_tables.h +++ b/engines/mohawk/detection_tables.h @@ -22,6 +22,13 @@ namespace Mohawk { +#define GAMEOPTION_PLAY_MYST_FLYBY GUIO_GAMEOPTIONS1 + +#define GUI_OPTIONS_MYST GUIO3(GUIO_NOASPECT, GUIO_NOSUBTITLES, GUIO_NOMIDI) +#define GUI_OPTIONS_MYST_ME GUIO4(GUIO_NOASPECT, GUIO_NOSUBTITLES, GUIO_NOMIDI, GAMEOPTION_PLAY_MYST_FLYBY) +#define GUI_OPTIONS_MYST_DEMO GUIO4(GUIO_NOASPECT, GUIO_NOSUBTITLES, GUIO_NOMIDI, GUIO_NOLAUNCHLOAD) +#define GUI_OPTIONS_MYST_MAKING_OF GUIO4(GUIO_NOASPECT, GUIO_NOSUBTITLES, GUIO_NOMIDI, GUIO_NOLAUNCHLOAD) + static const MohawkGameDescription gameDescriptions[] = { // Myst // English Windows 3.11 @@ -33,8 +40,8 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "ae3258c9c90128d274aa6a790b3ad181"), Common::EN_ANY, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -51,8 +58,8 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("DEMO.DAT", "c39303dd53fb5c4e7f3c23231c606cd0"), Common::EN_ANY, Common::kPlatformWindows, - ADGF_DEMO | ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_DEMO | ADGF_NO_FLAGS, + GUI_OPTIONS_MYST_DEMO }, GType_MYST, GF_DEMO, @@ -69,8 +76,8 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "4beb3366ed3f3b9bfb6e81a14a43bdcc"), Common::DE_DEU, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -87,8 +94,8 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "e0937cca1ab125e48e30dc3cd5046ddf"), Common::DE_DEU, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -105,8 +112,8 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "f7e7d7ca69934f1351b5acd4fe4d44c2"), Common::ES_ESP, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -123,8 +130,8 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "a5795ce1751fc42525e4f9a1859181d5"), Common::IT_ITA, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -141,8 +148,8 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "032c88e3b7e8db4ca475e7b7db9a66bb"), Common::JA_JPN, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -159,8 +166,8 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "d631d42567a941c67c78f2e491f4ea58"), Common::FR_FRA, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -172,13 +179,13 @@ static const MohawkGameDescription gameDescriptions[] = { // From clone2727 { { - "MakingOfMyst", + "makingofmyst", "", AD_ENTRY1("MAKING.DAT", "f6387e8f0f7b8a3e42c95294315d6a0e"), Common::EN_ANY, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST_MAKING_OF }, GType_MAKINGOF, 0, @@ -190,13 +197,13 @@ static const MohawkGameDescription gameDescriptions[] = { // From clone2727 { { - "MakingOfMyst", + "makingofmyst", "", AD_ENTRY1("MAKING.DAT", "03ff62607e64419ab2b6ebf7b7bcdf63"), Common::JA_JPN, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST_MAKING_OF }, GType_MAKINGOF, 0, @@ -213,8 +220,8 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "c4cae9f143b5947262e6cb2397e1617e"), Common::EN_ANY, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST_ME }, GType_MYST, GF_ME, @@ -231,8 +238,8 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "f88e0ace66dbca78eebdaaa1d3314ceb"), Common::DE_DEU, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST_ME }, GType_MYST, GF_ME, @@ -249,8 +256,8 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "aea81633b2d2ae498f09072fb87263b6"), Common::FR_FRA, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST_ME }, GType_MYST, GF_ME, @@ -267,8 +274,8 @@ static const MohawkGameDescription gameDescriptions[] = { AD_ENTRY1("MYST.DAT", "4a05771b60f4a69869838d01e85c9e80"), Common::PL_POL, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST_ME }, GType_MYST, GF_ME, @@ -330,6 +337,24 @@ static const MohawkGameDescription gameDescriptions[] = { }, // Riven: The Sequel to Myst + // Version 1.0 (5CD), 1.02 (DVD, From "Myst: La Trilogie") + // From gamin + { + { + "riven", + "", + AD_ENTRY1("a_Data.MHK", "aff2a384aaa9a0e0ec51010f708c5c04"), + Common::FR_FRA, + Common::kPlatformWindows, + ADGF_UNSTABLE, + GUIO1(GUIO_NOASPECT) + }, + GType_RIVEN, + 0, + 0, + }, + + // Riven: The Sequel to Myst // Version 1.0 (5CD) - Italian // From dodomorandi on bug #6629 { @@ -348,6 +373,23 @@ static const MohawkGameDescription gameDescriptions[] = { }, // Riven: The Sequel to Myst + // Version 1.0.0 (5CD) - Russian, Fargus + { + { + "riven", + "", + AD_ENTRY1s("a_Data.MHK", "2a840ed74fe5dc3a388bced674d379d5", 12024358), + Common::RU_RUS, + Common::kPlatformWindows, + ADGF_UNSTABLE, + GUIO1(GUIO_NOASPECT) + }, + GType_RIVEN, + 0, + 0, + }, + + // Riven: The Sequel to Myst // Version 1.1 (5CD) - Russian, Fargus { { @@ -364,33 +406,34 @@ static const MohawkGameDescription gameDescriptions[] = { 0, }, + // Riven: The Sequel to Myst - // Version 1.? (DVD, From "Myst 10th Anniversary Edition") - // From Clone2727 + // Version 1.0J (5CD) - Japanese + // From sev { { "riven", - "DVD", - AD_ENTRY1("a_Data.MHK", "08fcaa5d5a2a01d7a5a6960f497212fe"), - Common::EN_ANY, + "", + AD_ENTRY1s("a_Data.MHK", "3a2b4764979dc007a0e6ded64e4b7889", 10014314), + Common::JA_JPN, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO1(GUIO_NOASPECT) }, GType_RIVEN, - GF_DVD, + 0, 0, }, // Riven: The Sequel to Myst - // Version 1.0 (DVD, From "Myst: Die Trilogie") - // From DrMcCoy + // Version 1.? (DVD, From "Myst 10th Anniversary Edition") + // From Clone2727 { { "riven", - "", - AD_ENTRY1("a_Data.MHK", "a5fe1c91a6033eb6ee54b287578b74b9"), - Common::DE_DEU, + "DVD", + AD_ENTRY1("a_Data.MHK", "08fcaa5d5a2a01d7a5a6960f497212fe"), + Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO1(GUIO_NOASPECT) @@ -401,14 +444,14 @@ static const MohawkGameDescription gameDescriptions[] = { }, // Riven: The Sequel to Myst - // Version ? (DVD, From "Myst: La Trilogie") - // From gamin + // Version 1.0 (DVD, From "Myst: Die Trilogie") + // From DrMcCoy { { "riven", "", - AD_ENTRY1("a_Data.MHK", "aff2a384aaa9a0e0ec51010f708c5c04"), - Common::FR_FRA, + AD_ENTRY1("a_Data.MHK", "a5fe1c91a6033eb6ee54b287578b74b9"), + Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE, GUIO1(GUIO_NOASPECT) @@ -1871,6 +1914,23 @@ static const MohawkGameDescription gameDescriptions[] = { "Living Books Player" }, + // From Matthew Winder in bug#6557 + // v1.0E, English, Windows + { + { + "arthurbday", + "", + AD_ENTRY1s("AB16B.LB", "c169be346de7b0bbfcd18761fc0a3e49", 3093), + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO1(GUIO_NOASPECT) + }, + GType_LIVINGBOOKSV2, + 0, + 0, + }, + // From Torsten in bug#3422652 { { @@ -2093,6 +2153,22 @@ static const MohawkGameDescription gameDescriptions[] = { 0 }, + // From Matthew Winder in bug#6557 + { + { + "lilmonster", + "", + AD_ENTRY1s("lmasf.lb", "fcb665df1713d0411a41515efb20bebc", 4136), + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO1(GUIO_NOASPECT) + }, + GType_LIVINGBOOKSV2, + 0, + 0 + }, + // From afholman in bug#3309308 { { @@ -2697,8 +2773,8 @@ static const MohawkGameDescription fallbackDescs[] = { AD_ENTRY1(0, 0), Common::UNK_LANG, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -2707,13 +2783,13 @@ static const MohawkGameDescription fallbackDescs[] = { { { - "MakingOfMyst", + "makingofmyst", "unknown", AD_ENTRY1(0, 0), Common::UNK_LANG, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST_MAKING_OF }, GType_MAKINGOF, 0, @@ -2727,8 +2803,8 @@ static const MohawkGameDescription fallbackDescs[] = { AD_ENTRY1(0, 0), Common::UNK_LANG, Common::kPlatformWindows, - ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + ADGF_NO_FLAGS, + GUI_OPTIONS_MYST_ME }, GType_MYST, GF_ME, diff --git a/engines/mohawk/dialogs.cpp b/engines/mohawk/dialogs.cpp index f8aaf0f4af..38be98dfec 100644 --- a/engines/mohawk/dialogs.cpp +++ b/engines/mohawk/dialogs.cpp @@ -25,7 +25,6 @@ #include "gui/gui-manager.h" #include "gui/saveload.h" -#include "gui/ThemeEngine.h" #include "gui/widget.h" #include "common/system.h" #include "common/translation.h" @@ -89,27 +88,11 @@ enum { kQuitCmd = 'QUIT' }; -#ifdef ENABLE_MYST - -MystOptionsDialog::MystOptionsDialog(MohawkEngine_Myst* vm) : GUI::Dialog(0, 0, 360, 200), _vm(vm) { - // I18N: Option for fast scene switching - _zipModeCheckbox = new GUI::CheckboxWidget(this, 15, 10, 220, 15, _("~Z~ip Mode Activated"), 0, kZipCmd); - _transitionsCheckbox = new GUI::CheckboxWidget(this, 15, 30, 220, 15, _("~T~ransitions Enabled"), 0, kTransCmd); - // I18N: Drop book page - _dropPageButton = new GUI::ButtonWidget(this, 15, 60, 100, 25, _("~D~rop Page"), 0, kDropCmd); - - // Myst ME only has maps - if (_vm->getFeatures() & GF_ME) - _showMapButton = new GUI::ButtonWidget(this, 15, 95, 100, 25, _("Show ~M~ap"), 0, kMapCmd); - else - _showMapButton = 0; - - // Myst demo only has a menu - if (_vm->getFeatures() & GF_DEMO) - _returnToMenuButton = new GUI::ButtonWidget(this, 15, 95, 100, 25, _("Main Men~u~"), 0, kMenuCmd); - else - _returnToMenuButton = 0; +#if defined(ENABLE_MYST) || defined(ENABLE_RIVEN) +MohawkOptionsDialog::MohawkOptionsDialog(MohawkEngine *vm) : + GUI::Dialog(0, 0, 360, 200), + _vm(vm), _loadSlot(-1) { _loadButton = new GUI::ButtonWidget(this, 245, 25, 100, 25, _("~L~oad"), 0, kLoadCmd); _saveButton = new GUI::ButtonWidget(this, 245, 60, 100, 25, _("~S~ave"), 0, kSaveCmd); new GUI::ButtonWidget(this, 245, 95, 100, 25, _("~Q~uit"), 0, kQuitCmd); @@ -121,37 +104,21 @@ MystOptionsDialog::MystOptionsDialog(MohawkEngine_Myst* vm) : GUI::Dialog(0, 0, _saveDialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true); } -MystOptionsDialog::~MystOptionsDialog() { +MohawkOptionsDialog::~MohawkOptionsDialog() { delete _loadDialog; delete _saveDialog; } -void MystOptionsDialog::open() { - Dialog::open(); - - _dropPageButton->setEnabled(_vm->_gameState->_globals.heldPage != 0); - - if (_showMapButton) - _showMapButton->setEnabled(_vm->_scriptParser && - _vm->_scriptParser->getMap()); - - // Return to menu button is not enabled on the menu - if (_returnToMenuButton) - _returnToMenuButton->setEnabled(_vm->_scriptParser && - _vm->getCurStack() != kDemoStack); - - // Zip mode is disabled in the demo - if (_vm->getFeatures() & GF_DEMO) - _zipModeCheckbox->setEnabled(false); - - _zipModeCheckbox->setState(_vm->_gameState->_globals.zipMode); - _transitionsCheckbox->setState(_vm->_gameState->_globals.transitions); +void MohawkOptionsDialog::open() { + GUI::Dialog::open(); + _loadSlot = -1; _loadButton->setEnabled(_vm->canLoadGameStateCurrently()); _saveButton->setEnabled(_vm->canSaveGameStateCurrently()); } -void MystOptionsDialog::save() { + +void MohawkOptionsDialog::save() { int slot = _saveDialog->runModalWithCurrentTarget(); if (slot >= 0) { @@ -166,16 +133,18 @@ void MystOptionsDialog::save() { } } -void MystOptionsDialog::load() { - int slot = _loadDialog->runModalWithCurrentTarget(); +void MohawkOptionsDialog::load() { + // Do not load the game state from insite the dialog loop to + // avoid mouse cursor glitches (see bug #7164). Instead store + // the slot to load and let the code exectuting the dialog do + // the load after the dialog finished running. + _loadSlot = _loadDialog->runModalWithCurrentTarget(); - if (slot >= 0) { - _vm->loadGameState(slot); + if (_loadSlot >= 0) close(); - } } -void MystOptionsDialog::reflowLayout() { +void MohawkOptionsDialog::reflowLayout() { const int screenW = g_system->getOverlayWidth(); const int screenH = g_system->getOverlayHeight(); @@ -183,7 +152,73 @@ void MystOptionsDialog::reflowLayout() { _x = (screenW - getWidth()) / 2; _y = (screenH - getHeight()) / 2; - Dialog::reflowLayout(); + GUI::Dialog::reflowLayout(); +} + + +void MohawkOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) { + switch (cmd) { + case kLoadCmd: + load(); + break; + case kSaveCmd: + save(); + break; + case GUI::kCloseCmd: + close(); + break; + default: + GUI::Dialog::handleCommand(sender, cmd, data); + } +} + +#endif + +#ifdef ENABLE_MYST + +MystOptionsDialog::MystOptionsDialog(MohawkEngine_Myst* vm) : MohawkOptionsDialog(vm), _vm(vm) { + // I18N: Option for fast scene switching + _zipModeCheckbox = new GUI::CheckboxWidget(this, 15, 10, 220, 15, _("~Z~ip Mode Activated"), 0, kZipCmd); + _transitionsCheckbox = new GUI::CheckboxWidget(this, 15, 30, 220, 15, _("~T~ransitions Enabled"), 0, kTransCmd); + // I18N: Drop book page + _dropPageButton = new GUI::ButtonWidget(this, 15, 60, 100, 25, _("~D~rop Page"), 0, kDropCmd); + + // Myst ME only has maps + if (_vm->getFeatures() & GF_ME) + _showMapButton = new GUI::ButtonWidget(this, 15, 95, 100, 25, _("Show ~M~ap"), 0, kMapCmd); + else + _showMapButton = 0; + + // Myst demo only has a menu + if (_vm->getFeatures() & GF_DEMO) + _returnToMenuButton = new GUI::ButtonWidget(this, 15, 95, 100, 25, _("Main Men~u~"), 0, kMenuCmd); + else + _returnToMenuButton = 0; +} + +MystOptionsDialog::~MystOptionsDialog() { +} + +void MystOptionsDialog::open() { + MohawkOptionsDialog::open(); + + _dropPageButton->setEnabled(_vm->_gameState->_globals.heldPage != 0); + + if (_showMapButton) + _showMapButton->setEnabled(_vm->_scriptParser && + _vm->_scriptParser->getMap()); + + // Return to menu button is not enabled on the menu + if (_returnToMenuButton) + _returnToMenuButton->setEnabled(_vm->_scriptParser && + _vm->getCurStack() != kDemoStack); + + // Zip mode is disabled in the demo + if (_vm->getFeatures() & GF_DEMO) + _zipModeCheckbox->setEnabled(false); + + _zipModeCheckbox->setState(_vm->_gameState->_globals.zipMode); + _transitionsCheckbox->setState(_vm->_gameState->_globals.transitions); } void MystOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) { @@ -200,16 +235,14 @@ void MystOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, ui _vm->_needsShowDemoMenu = true; close(); break; - case kLoadCmd: - load(); - break; - case kSaveCmd: - save(); - break; case kQuitCmd: { - Common::Event eventQ; - eventQ.type = Common::EVENT_QUIT; - g_system->getEventManager()->pushEvent(eventQ); + if (_vm->getGameType() != GType_MAKINGOF) { + _vm->_needsShowCredits = true; + } else { + Common::Event eventQ; + eventQ.type = Common::EVENT_QUIT; + g_system->getEventManager()->pushEvent(eventQ); + } close(); } break; @@ -219,11 +252,8 @@ void MystOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, ui setResult(1); close(); break; - case GUI::kCloseCmd: - close(); - break; default: - GUI::Dialog::handleCommand(sender, cmd, data); + MohawkOptionsDialog::handleCommand(sender, cmd, data); } } @@ -231,19 +261,18 @@ void MystOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, ui #ifdef ENABLE_RIVEN -RivenOptionsDialog::RivenOptionsDialog(MohawkEngine_Riven* vm) : GUI::OptionsDialog("", 120, 120, 360, 200), _vm(vm) { - _zipModeCheckbox = new GUI::CheckboxWidget(this, 15, 10, 300, 15, _("~Z~ip Mode Activated"), 0, kZipCmd); - _waterEffectCheckbox = new GUI::CheckboxWidget(this, 15, 30, 300, 15, _("~W~ater Effect Enabled"), 0, kWaterCmd); - - new GUI::ButtonWidget(this, 95, 160, 120, 25, _("~O~K"), 0, GUI::kOKCmd); - new GUI::ButtonWidget(this, 225, 160, 120, 25, _("~C~ancel"), 0, GUI::kCloseCmd); +RivenOptionsDialog::RivenOptionsDialog(MohawkEngine_Riven* vm) : + MohawkOptionsDialog(vm), + _vm(vm) { + _zipModeCheckbox = new GUI::CheckboxWidget(this, 15, 10, 220, 15, _("~Z~ip Mode Activated"), 0, kZipCmd); + _waterEffectCheckbox = new GUI::CheckboxWidget(this, 15, 30, 220, 15, _("~W~ater Effect Enabled"), 0, kWaterCmd); } RivenOptionsDialog::~RivenOptionsDialog() { } void RivenOptionsDialog::open() { - Dialog::open(); + MohawkOptionsDialog::open(); _zipModeCheckbox->setState(_vm->_vars["azip"] != 0); _waterEffectCheckbox->setState(_vm->_vars["waterenabled"] != 0); @@ -251,17 +280,21 @@ void RivenOptionsDialog::open() { void RivenOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) { switch (cmd) { - case kZipCmd: + case GUI::kOKCmd: _vm->_vars["azip"] = _zipModeCheckbox->getState() ? 1 : 0; - break; - case kWaterCmd: _vm->_vars["waterenabled"] = _waterEffectCheckbox->getState() ? 1 : 0; + setResult(1); + close(); break; - case GUI::kCloseCmd: + case kQuitCmd: { + Common::Event eventQ; + eventQ.type = Common::EVENT_QUIT; + g_system->getEventManager()->pushEvent(eventQ); close(); break; + } default: - GUI::OptionsDialog::handleCommand(sender, cmd, data); + MohawkOptionsDialog::handleCommand(sender, cmd, data); } } diff --git a/engines/mohawk/dialogs.h b/engines/mohawk/dialogs.h index bc25c72a43..443f2fb67e 100644 --- a/engines/mohawk/dialogs.h +++ b/engines/mohawk/dialogs.h @@ -28,12 +28,13 @@ #include "common/events.h" #include "common/str.h" #include "gui/dialog.h" -#include "gui/options.h" -#include "gui/widget.h" -#include "gui/widgets/list.h" namespace GUI { class SaveLoadChooser; +class ButtonWidget; +class CheckboxWidget; +class CommandSender; +class StaticTextWidget; } namespace Mohawk { @@ -70,18 +71,48 @@ public: virtual void handleKeyDown(Common::KeyState state); }; +#if defined(ENABLE_MYST) || defined(ENABLE_RIVEN) + +class MohawkOptionsDialog : public GUI::Dialog { +public: + MohawkOptionsDialog(MohawkEngine *_vm); + virtual ~MohawkOptionsDialog(); + + virtual void open() override; + virtual void reflowLayout() override; + virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) override; + + int getLoadSlot() const {return _loadSlot;} + +private: + MohawkEngine *_vm; + + GUI::ButtonWidget *_loadButton; + GUI::ButtonWidget *_saveButton; + + GUI::SaveLoadChooser *_loadDialog; + GUI::SaveLoadChooser *_saveDialog; + + int _loadSlot; + + void save(); + void load(); +}; + +#endif + #ifdef ENABLE_MYST class MohawkEngine_Myst; -class MystOptionsDialog : public GUI::Dialog { +class MystOptionsDialog : public MohawkOptionsDialog { public: MystOptionsDialog(MohawkEngine_Myst *vm); - ~MystOptionsDialog(); - void open(); + virtual ~MystOptionsDialog(); + + virtual void open() override; + virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data); - virtual void reflowLayout() override; - virtual void handleCommand(GUI::CommandSender*, uint32, uint32); private: MohawkEngine_Myst *_vm; @@ -91,15 +122,6 @@ private: GUI::ButtonWidget *_dropPageButton; GUI::ButtonWidget *_showMapButton; GUI::ButtonWidget *_returnToMenuButton; - - GUI::ButtonWidget *_loadButton; - GUI::ButtonWidget *_saveButton; - - GUI::SaveLoadChooser *_loadDialog; - GUI::SaveLoadChooser *_saveDialog; - - void save(); - void load(); }; #endif @@ -108,15 +130,17 @@ private: class MohawkEngine_Riven; -class RivenOptionsDialog : public GUI::OptionsDialog { +class RivenOptionsDialog : public MohawkOptionsDialog { public: RivenOptionsDialog(MohawkEngine_Riven *vm); - ~RivenOptionsDialog(); - void open(); + virtual ~RivenOptionsDialog(); + + virtual void open() override; + virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) override; - virtual void handleCommand(GUI::CommandSender*, uint32, uint32); private: MohawkEngine_Riven *_vm; + GUI::CheckboxWidget *_zipModeCheckbox; GUI::CheckboxWidget *_waterEffectCheckbox; }; diff --git a/engines/mohawk/graphics.h b/engines/mohawk/graphics.h index 5f9b523e9a..f9fdeea15f 100644 --- a/engines/mohawk/graphics.h +++ b/engines/mohawk/graphics.h @@ -74,6 +74,10 @@ public: // Free all surfaces in the cache void clearCache(); + // findImage will search the cache to find the image. + // If not found, it will call decodeImage to get a new one. + MohawkSurface *findImage(uint16 id); + void preloadImage(uint16 image); virtual void setPalette(uint16 id); void copyAnimImageToScreen(uint16 image, int left = 0, int top = 0); @@ -85,10 +89,6 @@ public: protected: void copyAnimImageSectionToScreen(MohawkSurface *image, Common::Rect src, Common::Rect dest); - // findImage will search the cache to find the image. - // If not found, it will call decodeImage to get a new one. - MohawkSurface *findImage(uint16 id); - // decodeImage will always return a new image. virtual MohawkSurface *decodeImage(uint16 id) = 0; virtual Common::Array<MohawkSurface *> decodeImages(uint16 id); diff --git a/engines/mohawk/livingbooks.cpp b/engines/mohawk/livingbooks.cpp index 5af8fac901..6ee18d1576 100644 --- a/engines/mohawk/livingbooks.cpp +++ b/engines/mohawk/livingbooks.cpp @@ -144,6 +144,7 @@ MohawkEngine_LivingBooks::MohawkEngine_LivingBooks(OSystem *syst, const MohawkGa _rnd = new Common::RandomSource("livingbooks"); + _sound = NULL; _page = NULL; const Common::FSNode gameDataDir(ConfMan.get("path")); @@ -158,6 +159,7 @@ MohawkEngine_LivingBooks::~MohawkEngine_LivingBooks() { destroyPage(); delete _console; + delete _sound; delete _gfx; delete _rnd; _bookInfoFile.clear(); @@ -182,6 +184,7 @@ Common::Error MohawkEngine_LivingBooks::run() { error("Could not find xRes/yRes variables"); _gfx = new LBGraphics(this, _screenWidth, _screenHeight); + _sound = new Sound(this); if (getGameType() != GType_LIVINGBOOKSV1) _cursor = new LivingBooksCursorManager_v2(); @@ -3886,7 +3889,7 @@ bool LBMiniGameItem::togglePlaying(bool playing, bool restart) { // Go back to the menu if requested, otherwise go to the requested page if (returnToMenu) _vm->addNotifyEvent(NotifyEvent(kLBNotifyGoToControls, 1)); - else + else _vm->addNotifyEvent(NotifyEvent(kLBNotifyChangePage, destPage)); return false; diff --git a/engines/mohawk/livingbooks.h b/engines/mohawk/livingbooks.h index 1a265a1a02..cf67c1ee8e 100644 --- a/engines/mohawk/livingbooks.h +++ b/engines/mohawk/livingbooks.h @@ -714,6 +714,7 @@ public: Common::RandomSource *_rnd; + Sound *_sound; LBGraphics *_gfx; bool _needsRedraw, _needsUpdate; diff --git a/engines/mohawk/module.mk b/engines/mohawk/module.mk index 83e541e3e4..3fc118d2b6 100644 --- a/engines/mohawk/module.mk +++ b/engines/mohawk/module.mk @@ -57,6 +57,7 @@ MODULE_OBJS += \ riven_graphics.o \ riven_saveload.o \ riven_scripts.o \ + riven_sound.o \ riven_vars.o endif diff --git a/engines/mohawk/mohawk.cpp b/engines/mohawk/mohawk.cpp index d740d9479a..40290d4e4c 100644 --- a/engines/mohawk/mohawk.cpp +++ b/engines/mohawk/mohawk.cpp @@ -24,6 +24,7 @@ #include "common/error.h" #include "common/system.h" #include "common/textconsole.h" +#include "common/translation.h" #include "mohawk/mohawk.h" #include "mohawk/cursors.h" @@ -40,14 +41,12 @@ MohawkEngine::MohawkEngine(OSystem *syst, const MohawkGameDescription *gamedesc) // Setup mixer syncSoundSettings(); - _sound = 0; _video = 0; _pauseDialog = 0; _cursor = 0; } MohawkEngine::~MohawkEngine() { - delete _sound; delete _video; delete _pauseDialog; delete _cursor; @@ -58,22 +57,19 @@ MohawkEngine::~MohawkEngine() { } Common::Error MohawkEngine::run() { - _sound = new Sound(this); _video = new VideoManager(this); - _pauseDialog = new PauseDialog(this, "The game is paused. Press any key to continue."); + _pauseDialog = new PauseDialog(this, _("The game is paused. Press any key to continue.")); return Common::kNoError; } void MohawkEngine::pauseEngineIntern(bool pause) { + Engine::pauseEngineIntern(pause); + if (pause) { _video->pauseVideos(); - _sound->pauseSound(); - _sound->pauseSLST(); } else { _video->resumeVideos(); - _sound->resumeSound(); - _sound->resumeSLST(); _system->updateScreen(); } } diff --git a/engines/mohawk/mohawk.h b/engines/mohawk/mohawk.h index 6fa733e38e..bc0d642bce 100644 --- a/engines/mohawk/mohawk.h +++ b/engines/mohawk/mohawk.h @@ -20,8 +20,8 @@ * */ -#ifndef MOHAWK_H -#define MOHAWK_H +#ifndef MOHAWK_MOHAWK_H +#define MOHAWK_MOHAWK_H #include "common/scummsys.h" #include "common/array.h" @@ -100,7 +100,6 @@ public: bool hasFeature(EngineFeature f) const; - Sound *_sound; VideoManager *_video; CursorManager *_cursor; diff --git a/engines/mohawk/myst.cpp b/engines/mohawk/myst.cpp index 3bc2b2dccb..e887436e98 100644 --- a/engines/mohawk/myst.cpp +++ b/engines/mohawk/myst.cpp @@ -66,11 +66,6 @@ MohawkEngine_Myst::MohawkEngine_Myst(OSystem *syst, const MohawkGameDescription DebugMan.addDebugChannel(kDebugHelp, "Help", "Track Help File (HELP) Parsing"); DebugMan.addDebugChannel(kDebugCache, "Cache", "Track Resource Cache Accesses"); - // Engine tweaks - // Disabling this makes engine behavior as per - // original, including bugs, missing bits etc. :) - _tweaksEnabled = true; - _currentCursor = 0; _mainCursor = kDefaultMystCursor; _showResourceRects = false; @@ -80,6 +75,7 @@ MohawkEngine_Myst::MohawkEngine_Myst(OSystem *syst, const MohawkGameDescription _curResource = -1; _hoverResource = nullptr; + _sound = nullptr; _gfx = nullptr; _console = nullptr; _scriptParser = nullptr; @@ -93,6 +89,7 @@ MohawkEngine_Myst::~MohawkEngine_Myst() { DebugMan.clearAllDebugChannels(); delete _gfx; + delete _sound; delete _console; delete _scriptParser; delete _gameState; @@ -152,7 +149,7 @@ void MohawkEngine_Myst::cachePreload(uint32 tag, uint16 id) { } } - warning("cachePreload: Could not find a \'%s\' resource with ID %04x", tag2str(tag), id); + debugC(kDebugCache, "cachePreload: Could not find a \'%s\' resource with ID %04x", tag2str(tag), id); } static const char *mystFiles[] = { @@ -225,6 +222,7 @@ Common::Error MohawkEngine_Myst::run() { MohawkEngine::run(); _gfx = new MystGraphics(this); + _sound = new Sound(this); _console = new MystConsole(this); _gameState = new MystGameState(this, _saveFileMan); _optionsDialog = new MystOptionsDialog(this); @@ -236,11 +234,9 @@ Common::Error MohawkEngine_Myst::run() { // Load game from launcher/command line if requested if (ConfMan.hasKey("save_slot") && hasGameSaveSupport()) { - uint32 gameToLoad = ConfMan.getInt("save_slot"); - Common::StringArray savedGamesList = MystGameState::generateSaveGameList(); - if (gameToLoad > savedGamesList.size()) - error ("Could not find saved game"); - _gameState->load(savedGamesList[gameToLoad]); + int saveSlot = ConfMan.getInt("save_slot"); + if (!_gameState->load(saveSlot)) + error("Failed to load save game from slot %i", saveSlot); } else { // Start us on the first stack. if (getGameType() == GType_MAKINGOF) @@ -312,9 +308,12 @@ Common::Error MohawkEngine_Myst::run() { _needsPageDrop = false; _needsShowMap = false; _needsShowDemoMenu = false; + _needsShowCredits = false; _canSafelySaveLoad = true; runDialog(*_optionsDialog); + if (_optionsDialog->getLoadSlot() >= 0) + loadGameState(_optionsDialog->getLoadSlot()); _canSafelySaveLoad = false; if (_needsPageDrop) { @@ -331,6 +330,12 @@ Common::Error MohawkEngine_Myst::run() { changeToStack(kDemoStack, 2002, 0, 0); _needsShowDemoMenu = false; } + + if (_needsShowCredits) { + _cursor->hideCursor(); + changeToStack(kCreditsStack, 10000, 0, 0); + _needsShowCredits = false; + } break; default: break; @@ -396,6 +401,25 @@ bool MohawkEngine_Myst::skippableWait(uint32 duration) { return skipped; } +void MohawkEngine_Myst::pollAndDiscardEvents() { + // Poll the events to update the mouse cursor position + Common::Event event; + while (_system->getEventManager()->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + switch (event.kbd.keycode) { + case Common::KEYCODE_SPACE: + pauseGame(); + break; + default: + break; + } + default: + break; + } + } +} + void MohawkEngine_Myst::changeToStack(uint16 stack, uint16 card, uint16 linkSrcSound, uint16 linkDstSound) { debug(2, "changeToStack(%d)", stack); @@ -503,8 +527,9 @@ void MohawkEngine_Myst::changeToStack(uint16 stack, uint16 card, uint16 linkSrcS flyby = "stoneship flyby"; break; // Myst Flyby Movie not used in Original Masterpiece Edition Engine + // We play it when first arriving on Myst, and if the user has chosen so. case kMystStack: - if (_tweaksEnabled) + if (ConfMan.getBool("playmystflyby") && card == 4134) flyby = "myst flyby"; break; case kMechanicalStack: @@ -603,7 +628,8 @@ void MohawkEngine_Myst::changeToCard(uint16 card, TransitionType transition) { _gfx->runTransition(transition, Common::Rect(544, 333), 10, 0); } else { _gfx->copyBackBufferToScreen(Common::Rect(544, 333)); - _needsUpdate = true; + _system->updateScreen(); + _needsUpdate = false; } } @@ -1080,19 +1106,14 @@ void MohawkEngine_Myst::loadResources() { } Common::Error MohawkEngine_Myst::loadGameState(int slot) { - if (_gameState->load(MystGameState::generateSaveGameList()[slot])) + if (_gameState->load(slot)) return Common::kNoError; return Common::kUnknownError; } Common::Error MohawkEngine_Myst::saveGameState(int slot, const Common::String &desc) { - Common::StringArray saveList = MystGameState::generateSaveGameList(); - - if ((uint)slot < saveList.size()) - MystGameState::deleteSave(saveList[slot]); - - return _gameState->save(desc) ? Common::kNoError : Common::kUnknownError; + return _gameState->save(slot, desc) ? Common::kNoError : Common::kUnknownError; } bool MohawkEngine_Myst::hasGameSaveSupport() const { diff --git a/engines/mohawk/myst.h b/engines/mohawk/myst.h index 0803c69e55..2414b71cb1 100644 --- a/engines/mohawk/myst.h +++ b/engines/mohawk/myst.h @@ -187,19 +187,21 @@ public: uint16 getMainCursor() { return _mainCursor; } void checkCursorHints(); MystArea *updateCurrentResource(); + void pollAndDiscardEvents(); bool skippableWait(uint32 duration); MystSoundBlock readSoundBlock(Common::ReadStream *stream) const; void applySoundBlock(const MystSoundBlock &block); - bool _tweaksEnabled; bool _needsUpdate; bool _needsPageDrop; bool _needsShowMap; bool _needsShowDemoMenu; + bool _needsShowCredits; bool _showResourceRects; + Sound *_sound; MystGraphics *_gfx; MystGameState *_gameState; MystScriptParser *_scriptParser; diff --git a/engines/mohawk/myst_areas.cpp b/engines/mohawk/myst_areas.cpp index 59871ed49f..8514cd5406 100644 --- a/engines/mohawk/myst_areas.cpp +++ b/engines/mohawk/myst_areas.cpp @@ -42,7 +42,7 @@ MystArea::MystArea(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream _rect.top = rlstStream->readSint16LE(); if (_rect.top == -1) { - warning("Invalid _rect.top of -1 found - clamping to 0"); + debugC(kDebugResource, "Invalid _rect.top of -1 found - clamping to 0"); _rect.top = 0; } @@ -240,6 +240,7 @@ VideoHandle MystAreaVideo::playMovie() { } else { // Resume the video handle->pause(false); + handle->start(); } if (_playBlocking) { @@ -250,6 +251,20 @@ VideoHandle MystAreaVideo::playMovie() { return handle; } +VideoHandle MystAreaVideo::getMovieHandle() { + // If the video is already in the manager, just return the handle + VideoHandle handle = _vm->_video->findVideoHandle(_videoFile); + if (!handle) { + // If the video has not been loaded yet, do it but don't start playing it + handle = _vm->_video->playMovie(_videoFile); + if (!handle) + error("Failed to open '%s'", _videoFile.c_str()); + handle->stop(); + } + + return handle; +} + void MystAreaVideo::handleCardChange() { if (_playOnCardChange) playMovie(); diff --git a/engines/mohawk/myst_areas.h b/engines/mohawk/myst_areas.h index 09ec6a2742..b19a2df9e2 100644 --- a/engines/mohawk/myst_areas.h +++ b/engines/mohawk/myst_areas.h @@ -108,6 +108,8 @@ public: MystAreaVideo(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent); VideoHandle playMovie(); + VideoHandle getMovieHandle(); + void handleCardChange() override; bool isPlaying(); void setDirection(int16 direction) { _direction = direction; } diff --git a/engines/mohawk/myst_graphics.cpp b/engines/mohawk/myst_graphics.cpp index 5db9697a78..333da402fa 100644 --- a/engines/mohawk/myst_graphics.cpp +++ b/engines/mohawk/myst_graphics.cpp @@ -47,8 +47,7 @@ MystGraphics::MystGraphics(MohawkEngine_Myst* vm) : GraphicsManager(), _vm(vm) { } else { // Paletted initGraphics(_viewport.width(), _viewport.height(), true); - setBasePalette(); - setPaletteToScreen(); + clearScreenPalette(); } _pixelFormat = _vm->_system->getScreenFormat(); @@ -86,7 +85,7 @@ MohawkSurface *MystGraphics::decodeImage(uint16 id) { bool isPict = false; - if (_vm->getFeatures() & GF_ME) { + if ((_vm->getFeatures() & GF_ME) && dataStream->size() > 512 + 10 + 4) { // Here we detect whether it's really a PICT or a WDIB. Since a MystBitmap // would be compressed, there's no way to detect for the BM without a hack. // So, we search for the PICT version opcode for detection. @@ -109,8 +108,11 @@ MohawkSurface *MystGraphics::decodeImage(uint16 id) { } else { mhkSurface = _bmpDecoder->decodeImage(dataStream); - if (_vm->getFeatures() & GF_ME) + if (_vm->getFeatures() & GF_ME) { mhkSurface->convertToTrueColor(); + } else { + remapSurfaceToSystemPalette(mhkSurface); + } } assert(mhkSurface); @@ -204,7 +206,7 @@ void MystGraphics::copyImageSectionToBackBuffer(uint16 image, Common::Rect src, if (!(_vm->getFeatures() & GF_ME)) { // Make sure the palette is set assert(mhkSurface->getPalette()); - memcpy(_palette + 10 * 3, mhkSurface->getPalette() + 10 * 3, (256 - 10 * 2) * 3); + memcpy(_palette, mhkSurface->getPalette(), 256 * 3); setPaletteToScreen(); } } @@ -241,6 +243,7 @@ void MystGraphics::runTransition(TransitionType type, Common::Rect rect, uint16 area.right = area.left + step; _vm->_system->delayMillis(delay); + _vm->pollAndDiscardEvents(); copyBackBufferToScreen(area); _vm->_system->updateScreen(); @@ -264,6 +267,7 @@ void MystGraphics::runTransition(TransitionType type, Common::Rect rect, uint16 area.left = area.right - step; _vm->_system->delayMillis(delay); + _vm->pollAndDiscardEvents(); copyBackBufferToScreen(area); _vm->_system->updateScreen(); @@ -307,6 +311,7 @@ void MystGraphics::runTransition(TransitionType type, Common::Rect rect, uint16 area.bottom = area.top + step; _vm->_system->delayMillis(delay); + _vm->pollAndDiscardEvents(); copyBackBufferToScreen(area); _vm->_system->updateScreen(); @@ -330,6 +335,7 @@ void MystGraphics::runTransition(TransitionType type, Common::Rect rect, uint16 area.top = area.bottom - step; _vm->_system->delayMillis(delay); + _vm->pollAndDiscardEvents(); copyBackBufferToScreen(area); _vm->_system->updateScreen(); @@ -454,6 +460,7 @@ void MystGraphics::transitionDissolve(Common::Rect rect, uint step) { } _vm->_system->unlockScreen(); + _vm->pollAndDiscardEvents(); _vm->_system->updateScreen(); } @@ -473,6 +480,7 @@ void MystGraphics::transitionSlideToLeft(Common::Rect rect, uint16 steps, uint16 simulatePreviousDrawDelay(dstRect); _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); + _vm->pollAndDiscardEvents(); _vm->_system->updateScreen(); } @@ -498,6 +506,7 @@ void MystGraphics::transitionSlideToRight(Common::Rect rect, uint16 steps, uint1 simulatePreviousDrawDelay(dstRect); _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); + _vm->pollAndDiscardEvents(); _vm->_system->updateScreen(); } @@ -523,6 +532,7 @@ void MystGraphics::transitionSlideToTop(Common::Rect rect, uint16 steps, uint16 simulatePreviousDrawDelay(dstRect); _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); + _vm->pollAndDiscardEvents(); _vm->_system->updateScreen(); } @@ -549,6 +559,7 @@ void MystGraphics::transitionSlideToBottom(Common::Rect rect, uint16 steps, uint simulatePreviousDrawDelay(dstRect); _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); + _vm->pollAndDiscardEvents(); _vm->_system->updateScreen(); } @@ -573,6 +584,7 @@ void MystGraphics::transitionPartialToRight(Common::Rect rect, uint32 width, uin simulatePreviousDrawDelay(dstRect); _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); + _vm->pollAndDiscardEvents(); _vm->_system->updateScreen(); } @@ -594,6 +606,7 @@ void MystGraphics::transitionPartialToLeft(Common::Rect rect, uint32 width, uint simulatePreviousDrawDelay(dstRect); _vm->_system->copyRectToScreen(_backBuffer->getBasePtr(dstRect.left, dstRect.top), _backBuffer->pitch, srcRect.left, srcRect.top, srcRect.width(), srcRect.height()); + _vm->pollAndDiscardEvents(); _vm->_system->updateScreen(); } @@ -703,10 +716,10 @@ void MystGraphics::clearScreenPalette() { _vm->_system->getPaletteManager()->setPalette(palette, 0, 256); } -void MystGraphics::setBasePalette() { +void MystGraphics::remapSurfaceToSystemPalette(MohawkSurface *mhkSurface) { // Entries [0, 9] of the palette static const byte lowPalette[] = { - 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00, @@ -729,15 +742,68 @@ void MystGraphics::setBasePalette() { 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, - 0x00, 0x00, 0x00 + 0xFF, 0xFF, 0xFF }; - // Note that 0 and 255 are different from normal Windows. - // Myst seems to hack that to white, resp. black (probably for Mac compat). + byte *originalPalette = mhkSurface->getPalette(); + + // The target palette is made of the Windows reserved palette, and colors 10 to 245 + // of the bitmap palette. Entries 0 to 9 and 246 to 255 of the bitmap palette are + // discarded. + byte targetPalette[256 * 3]; + memcpy(targetPalette, lowPalette, sizeof(lowPalette)); + memcpy(targetPalette + sizeof(lowPalette), originalPalette + sizeof(lowPalette), sizeof(_palette) - sizeof(lowPalette) - sizeof(highPalette)); + memcpy(targetPalette + sizeof(_palette) - sizeof(highPalette), highPalette, sizeof(highPalette)); + + // Remap the discarded entries from the bitmap palette using the target palette. + byte lowColorMap[ARRAYSIZE(lowPalette) / 3]; + byte highColorMap[ARRAYSIZE(highPalette) / 3]; + + for (uint i = 0; i < ARRAYSIZE(lowColorMap); i++) { + uint colorIndex = 3 * i; + byte red = originalPalette[colorIndex + 0]; + byte green = originalPalette[colorIndex + 1]; + byte blue = originalPalette[colorIndex + 2]; + + lowColorMap[i] = getColorIndex(targetPalette, red, green, blue); + } + + for (uint i = 0; i < ARRAYSIZE(highColorMap); i++) { + uint colorIndex = 3 * (i + 246); + byte red = originalPalette[colorIndex + 0]; + byte green = originalPalette[colorIndex + 1]; + byte blue = originalPalette[colorIndex + 2]; + + highColorMap[i] = getColorIndex(targetPalette, red, green, blue); + } + + // Replace the original palette with the target palette + memcpy(originalPalette, targetPalette, sizeof(targetPalette)); + + // Remap the pixel data to the target palette + Graphics::Surface *surface = mhkSurface->getSurface(); + byte *pixels = (byte *) surface->getPixels(); + + for (int i = 0; i < surface->w * surface->h; i++) { + if (pixels[i] < ARRAYSIZE(lowColorMap)) { + pixels[i] = lowColorMap[pixels[i]]; + } else if (pixels[i] >= 246) { + pixels[i] = highColorMap[pixels[i] - 246]; + } + } +} + +byte MystGraphics::getColorIndex(const byte *palette, byte red, byte green, byte blue) { + for (uint i = 0; i < 256; i++) { + if (palette[(3 * i) + 0] == red && palette[(3 * i) + 1] == green && palette[(3 * i) + 2] == blue) { + return i; + } + } - memcpy(_palette, lowPalette, sizeof(lowPalette)); - memset(_palette + sizeof(lowPalette), 0, sizeof(_palette) - sizeof(lowPalette) - sizeof(highPalette)); - memcpy(_palette + sizeof(_palette) - sizeof(highPalette), highPalette, sizeof(highPalette)); + // GDI actually chooses the nearest color if no exact match is found, + // but this should not happen in Myst + debug(1, "Color (%d, %d, %d) not in target palette", red, green, blue); + return 0; } void MystGraphics::setPaletteToScreen() { diff --git a/engines/mohawk/myst_graphics.h b/engines/mohawk/myst_graphics.h index 93e388cb83..cd09a53a3a 100644 --- a/engines/mohawk/myst_graphics.h +++ b/engines/mohawk/myst_graphics.h @@ -56,7 +56,6 @@ public: void fadeFromBlack(); void clearScreenPalette(); - void setBasePalette(); void setPaletteToScreen(); const byte *getPalette() const { return _palette; } @@ -86,6 +85,9 @@ private: void transitionSlideToBottom(Common::Rect rect, uint16 steps, uint16 delay); void transitionPartialToRight(Common::Rect rect, uint32 width, uint32 steps); void transitionPartialToLeft(Common::Rect rect, uint32 width, uint32 steps); + + void remapSurfaceToSystemPalette(MohawkSurface *mhkSurface); + byte getColorIndex(const byte *palette, byte red, byte green, byte blue); }; } // End of namespace Mohawk diff --git a/engines/mohawk/myst_scripts.cpp b/engines/mohawk/myst_scripts.cpp index 04e7c5a9b7..596180ddb2 100644 --- a/engines/mohawk/myst_scripts.cpp +++ b/engines/mohawk/myst_scripts.cpp @@ -31,7 +31,6 @@ #include "common/system.h" #include "common/memstream.h" #include "common/textconsole.h" -#include "gui/message.h" namespace Mohawk { @@ -685,9 +684,14 @@ void MystScriptParser::o_changeBackgroundSound(uint16 op, uint16 var, uint16 arg // Used on Channelwood Card 3225 with argc = 8 i.e. Conditional Sound List debugC(kDebugScript, "Opcode %d: Process Sound Block", op); - Common::MemoryReadStream stream = Common::MemoryReadStream((const byte *) argv, argc * sizeof(uint16)); + Common::MemoryWriteStreamDynamic writeStream = Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES); + for (uint i = 0; i < argc; i++) { + writeStream.writeUint16LE(argv[i]); + } + + Common::MemoryReadStream readStream = Common::MemoryReadStream(writeStream.getData(), writeStream.size()); - MystSoundBlock soundBlock = _vm->readSoundBlock(&stream); + MystSoundBlock soundBlock = _vm->readSoundBlock(&readStream); _vm->applySoundBlock(soundBlock); } diff --git a/engines/mohawk/myst_stacks/channelwood.cpp b/engines/mohawk/myst_stacks/channelwood.cpp index 659c5dcdf2..21c3042359 100644 --- a/engines/mohawk/myst_stacks/channelwood.cpp +++ b/engines/mohawk/myst_stacks/channelwood.cpp @@ -621,24 +621,32 @@ void Channelwood::o_hologramMonitor(uint16 op, uint16 var, uint16 argc, uint16 * switch (button) { case 0: handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monalgh", kChannelwoodStack)); + if (!handle) + error("Failed to open monalgh movie"); + handle->moveTo(227, 70); break; case 1: handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monamth", kChannelwoodStack)); + if (!handle) + error("Failed to open monamth movie"); + handle->moveTo(227, 70); break; case 2: handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monasirs", kChannelwoodStack)); + if (!handle) + error("Failed to open monasirs movie"); + handle->moveTo(227, 70); break; case 3: handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monsmsg", kChannelwoodStack)); + if (!handle) + error("Failed to open monsmsg movie"); + handle->moveTo(226, 68); break; default: warning("Opcode %d Control Variable Out of Range", op); break; } - - // Move the video to the right location - if (handle) - handle->moveTo(227, 70); } } diff --git a/engines/mohawk/myst_stacks/credits.cpp b/engines/mohawk/myst_stacks/credits.cpp index 6548dd3171..c382263f7c 100644 --- a/engines/mohawk/myst_stacks/credits.cpp +++ b/engines/mohawk/myst_stacks/credits.cpp @@ -28,7 +28,6 @@ #include "mohawk/myst_stacks/credits.h" #include "common/system.h" -#include "gui/message.h" namespace Mohawk { namespace MystStacks { @@ -67,8 +66,10 @@ void Credits::runPersistentScripts() { _curImage++; // After the 6th image has shown, it's time to quit - if (_curImage == 7) + if (_curImage == 7) { _vm->quitGame(); + return; + } // Draw next image _vm->drawCardBackground(); diff --git a/engines/mohawk/myst_stacks/intro.cpp b/engines/mohawk/myst_stacks/intro.cpp index 1d733d8100..f448108199 100644 --- a/engines/mohawk/myst_stacks/intro.cpp +++ b/engines/mohawk/myst_stacks/intro.cpp @@ -28,8 +28,6 @@ #include "mohawk/video.h" #include "mohawk/myst_stacks/intro.h" -#include "gui/message.h" - namespace Mohawk { namespace MystStacks { diff --git a/engines/mohawk/myst_stacks/makingof.cpp b/engines/mohawk/myst_stacks/makingof.cpp index 1059fd0c5e..a0a1f359ba 100644 --- a/engines/mohawk/myst_stacks/makingof.cpp +++ b/engines/mohawk/myst_stacks/makingof.cpp @@ -27,8 +27,6 @@ #include "mohawk/video.h" #include "mohawk/myst_stacks/makingof.h" -#include "gui/message.h" - namespace Mohawk { namespace MystStacks { diff --git a/engines/mohawk/myst_stacks/mechanical.cpp b/engines/mohawk/myst_stacks/mechanical.cpp index 3214c643a5..3324c9a22d 100644 --- a/engines/mohawk/myst_stacks/mechanical.cpp +++ b/engines/mohawk/myst_stacks/mechanical.cpp @@ -667,7 +667,7 @@ void Mechanical::o_elevatorTopMovie(uint16 op, uint16 var, uint16 argc, uint16 * void Mechanical::o_fortressRotationSetPosition(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Set fortress position", op); - VideoHandle gears = _fortressRotationGears->playMovie(); + VideoHandle gears = _fortressRotationGears->getMovieHandle(); uint32 moviePosition = Audio::Timestamp(gears->getTime(), 600).totalNumberOfFrames(); // Myst ME short movie workaround, explained in o_fortressRotation_init @@ -801,7 +801,7 @@ void Mechanical::o_elevatorRotation_init(uint16 op, uint16 var, uint16 argc, uin } void Mechanical::fortressRotation_run() { - VideoHandle gears = _fortressRotationGears->playMovie(); + VideoHandle gears = _fortressRotationGears->getMovieHandle(); double oldRate = gears->getRate().toDouble(); uint32 moviePosition = Audio::Timestamp(gears->getTime(), 600).totalNumberOfFrames(); @@ -949,7 +949,7 @@ void Mechanical::fortressSimulation_run() { _fortressSimulationInit = false; } else { - VideoHandle holo = _fortressSimulationHolo->playMovie(); + VideoHandle holo = _fortressSimulationHolo->getMovieHandle(); double oldRate = holo->getRate().toDouble(); diff --git a/engines/mohawk/myst_stacks/myst.cpp b/engines/mohawk/myst_stacks/myst.cpp index 5033f0fef8..f9ba6a42fa 100644 --- a/engines/mohawk/myst_stacks/myst.cpp +++ b/engines/mohawk/myst_stacks/myst.cpp @@ -33,8 +33,6 @@ #include "common/system.h" #include "common/textconsole.h" -#include "gui/message.h" - namespace Mohawk { namespace MystStacks { @@ -52,6 +50,7 @@ Myst::Myst(MohawkEngine_Myst *vm) : _cabinDoorOpened = 0; _cabinHandleDown = 0; _cabinMatchState = 2; + _cabinGaugeMovieEnabled = false; _matchBurning = false; _tree = nullptr; _treeAlcove = nullptr; @@ -2142,7 +2141,7 @@ void Myst::tree_run() { // Check if alcove is accessible treeSetAlcoveAccessible(); - if (_cabinGaugeMovie) { + if (_cabinGaugeMovieEnabled) { Common::Rational rate = boilerComputeGaugeRate(pressure, delay); boilerResetGauge(rate); } @@ -2313,7 +2312,7 @@ void Myst::o_rocketPianoStart(uint16 op, uint16 var, uint16 argc, uint16 *argv) dest.bottom = 332 - rect.top; // Draw pressed piano key - _vm->_gfx->copyImageSectionToScreen(key->getSubImage(0).wdib, src, dest); + _vm->_gfx->copyImageSectionToScreen(key->getSubImage(1).wdib, src, dest); _vm->_system->updateScreen(); // Play note @@ -3088,6 +3087,8 @@ void Myst::clockReset() { } void Myst::clockResetWeight() { + _vm->_sound->replaceSoundMyst(9113); + _clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack)); if (!_clockWeightVideo) error("Failed to open cl1wlfch movie"); @@ -3270,7 +3271,7 @@ void Myst::towerRotationMapDrawLine(const Common::Point ¢er, const Common::P color = pf.RGBToColor(0xFF, 0, 0); // Red } else { if (!_towerRotationOverSpot) - color = 0x00; // White + color = 0xFF; // White else color = 0xF9; // Red } @@ -3608,7 +3609,7 @@ void Myst::gullsFly2_run() { VideoHandle handle = _vm->_video->playMovie(_vm->wrapMovieFilename(gulls[video], kMystStack)); if (!handle) error("Failed to open gulls movie"); - + handle->moveTo(424, 0); _gullsNextTime = time + _vm->_rnd->getRandomNumber(16667) + 13334; } @@ -3703,6 +3704,8 @@ void Myst::boilerGaugeInit() { frame = Audio::Timestamp(0, 0, 600); _vm->_video->drawVideoFrame(_cabinGaugeMovie, frame); + + _cabinGaugeMovieEnabled = true; } void Myst::o_rocketSliders_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -3842,6 +3845,8 @@ void Myst::o_boiler_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) { _cabinGaugeMovie = VideoHandle(); _cabinFireMovie = VideoHandle(); + + _cabinGaugeMovieEnabled = false; } void Myst::o_generatorControlRoom_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) { diff --git a/engines/mohawk/myst_stacks/myst.h b/engines/mohawk/myst_stacks/myst.h index 9d66b798c5..6e2f7cc1c8 100644 --- a/engines/mohawk/myst_stacks/myst.h +++ b/engines/mohawk/myst_stacks/myst.h @@ -261,6 +261,8 @@ protected: uint32 _matchGoOutTime; // 144 VideoHandle _cabinFireMovie; // 240 + + bool _cabinGaugeMovieEnabled; VideoHandle _cabinGaugeMovie; // 244 bool _boilerPressureIncreasing; diff --git a/engines/mohawk/myst_stacks/selenitic.cpp b/engines/mohawk/myst_stacks/selenitic.cpp index 2617bd04aa..454435cf92 100644 --- a/engines/mohawk/myst_stacks/selenitic.cpp +++ b/engines/mohawk/myst_stacks/selenitic.cpp @@ -31,7 +31,6 @@ #include "common/system.h" #include "common/textconsole.h" -#include "gui/message.h" namespace Mohawk { namespace MystStacks { @@ -672,6 +671,11 @@ void Selenitic::soundReceiverUpdate() { } void Selenitic::soundReceiverDrawView() { + soundReceiverSetSubimageRect(); + soundReceiverDrawAngle(); +} + +void Selenitic::soundReceiverSetSubimageRect() const { uint32 left = ((*_soundReceiverPosition) * 1800) / 3600; Common::Rect rect = _soundReceiverViewer->getSubImage(0).rect; @@ -681,8 +685,6 @@ void Selenitic::soundReceiverDrawView() { _soundReceiverViewer->setSubImageRect(0, rect); _soundReceiverViewer->drawConditionalDataToScreen(0); - - soundReceiverDrawAngle(); } void Selenitic::soundReceiverDrawAngle() { @@ -948,10 +950,13 @@ void Selenitic::soundReceiver_run() { if (_soundReceiverDirection) { uint32 currentTime = _vm->_system->getMillis(); - if (_soundReceiverSpeed == 50 && currentTime > _soundReceiverStartTime + 500) - soundReceiverIncreaseSpeed(); - else if (currentTime > _soundReceiverStartTime + 1000) - soundReceiverIncreaseSpeed(); + if (_soundReceiverSpeed == 50 && currentTime > _soundReceiverStartTime + 500) { + soundReceiverIncreaseSpeed(); + _soundReceiverStartTime = currentTime; + } else if (currentTime > _soundReceiverStartTime + 1000) { + soundReceiverIncreaseSpeed(); + _soundReceiverStartTime = currentTime; + } if (currentTime > _soundReceiverStartTime + 100) soundReceiverUpdate(); @@ -1080,6 +1085,8 @@ void Selenitic::o_soundReceiver_init(uint16 op, uint16 var, uint16 argc, uint16 _soundReceiverPosition = &_state.soundReceiverPositions[currentSource]; _soundReceiverCurrentSource = _soundReceiverSources[currentSource]; + soundReceiverSetSubimageRect(); + _soundReceiverSigmaPressed = false; } diff --git a/engines/mohawk/myst_stacks/selenitic.h b/engines/mohawk/myst_stacks/selenitic.h index ffd8941f2c..fc9649755d 100644 --- a/engines/mohawk/myst_stacks/selenitic.h +++ b/engines/mohawk/myst_stacks/selenitic.h @@ -117,6 +117,7 @@ private: void soundReceiverLeftRight(uint direction); void soundReceiverUpdate(); + void soundReceiverSetSubimageRect() const; void soundReceiverDrawView(); void soundReceiverDrawAngle(); void soundReceiverIncreaseSpeed(); diff --git a/engines/mohawk/myst_stacks/slides.cpp b/engines/mohawk/myst_stacks/slides.cpp index a1413f0d71..0560608b24 100644 --- a/engines/mohawk/myst_stacks/slides.cpp +++ b/engines/mohawk/myst_stacks/slides.cpp @@ -29,7 +29,6 @@ #include "mohawk/myst_stacks/slides.h" #include "common/system.h" -#include "gui/message.h" namespace Mohawk { namespace MystStacks { diff --git a/engines/mohawk/myst_state.cpp b/engines/mohawk/myst_state.cpp index 06cd69b23c..213a976413 100644 --- a/engines/mohawk/myst_state.cpp +++ b/engines/mohawk/myst_state.cpp @@ -106,16 +106,12 @@ MystGameState::MystGameState(MohawkEngine_Myst *vm, Common::SaveFileManager *sav MystGameState::~MystGameState() { } -Common::StringArray MystGameState::generateSaveGameList() { - return g_system->getSavefileManager()->listSavefiles("*.mys"); -} - -bool MystGameState::load(const Common::String &filename) { - if (!loadState(filename)) { +bool MystGameState::load(int slot) { + if (!loadState(slot)) { return false; } - loadMetadata(filename); + loadMetadata(slot); // Set Channelwood elevator state to down, because we start on the lower level _channelwood.elevatorState = 0; @@ -124,6 +120,7 @@ bool MystGameState::load(const Common::String &filename) { _vm->changeToStack(kIntroStack, 5, 0, 0); // Set our default cursor + _vm->_cursor->showCursor(); if (_globals.heldPage == 0 || _globals.heldPage > 13) _vm->setMainCursor(kDefaultMystCursor); else if (_globals.heldPage < 7) @@ -136,7 +133,8 @@ bool MystGameState::load(const Common::String &filename) { return true; } -bool MystGameState::loadState(const Common::String &filename) { +bool MystGameState::loadState(int slot) { + Common::String filename = buildSaveFilename(slot); Common::InSaveFile *loadFile = _saveFileMan->openForLoading(filename); if (!loadFile) { return false; @@ -160,9 +158,10 @@ bool MystGameState::loadState(const Common::String &filename) { return true; } -void MystGameState::loadMetadata(const Common::String &filename) { +void MystGameState::loadMetadata(int slot) { // Open the metadata file - Common::InSaveFile *metadataFile = openMetadataFile(filename); + Common::String filename = buildMetadataFilename(slot); + Common::InSaveFile *metadataFile = _vm->getSaveFileManager()->openForLoading(filename); if (!metadataFile) { return; } @@ -179,25 +178,19 @@ void MystGameState::loadMetadata(const Common::String &filename) { delete metadataFile; } -bool MystGameState::save(const Common::String &filename) { - // Make sure the description does not have an extension - Common::String desc = filename; - if (filename.hasSuffix(".mys") || filename.hasSuffix(".MYS")) { - desc = removeExtension(filename); - } - - if (!saveState(desc)) { +bool MystGameState::save(int slot, const Common::String &desc) { + if (!saveState(slot)) { return false; } updateMetadateForSaving(desc); - return saveMetadata(desc); + return saveMetadata(slot); } -bool MystGameState::saveState(const Common::String &desc) { +bool MystGameState::saveState(int slot) { // Make sure we have the right extension - Common::String filename = desc + ".mys"; + Common::String filename = buildSaveFilename(slot); Common::OutSaveFile *saveFile = _saveFileMan->openForSaving(filename); if (!saveFile) { return false; @@ -213,6 +206,14 @@ bool MystGameState::saveState(const Common::String &desc) { return true; } +Common::String MystGameState::buildSaveFilename(int slot) { + return Common::String::format("myst-%03d.mys", slot); +} + +Common::String MystGameState::buildMetadataFilename(int slot) { + return Common::String::format("myst-%03d.mym", slot); +} + void MystGameState::updateMetadateForSaving(const Common::String &desc) { // Update save creation info TimeDate t; @@ -226,10 +227,10 @@ void MystGameState::updateMetadateForSaving(const Common::String &desc) { _metadata.totalPlayTime = _vm->getTotalPlayTime(); } -bool MystGameState::saveMetadata(const Common::String &desc) { +bool MystGameState::saveMetadata(int slot) { // Write the metadata to a separate file so that the save files // are still compatible with the original engine - Common::String metadataFilename = desc + ".mym"; + Common::String metadataFilename = buildMetadataFilename(slot); Common::OutSaveFile *metadataFile = _saveFileMan->openForSaving(metadataFilename); if (!metadataFile) { return false; @@ -248,14 +249,12 @@ bool MystGameState::saveMetadata(const Common::String &desc) { return true; } -SaveStateDescriptor MystGameState::querySaveMetaInfos(const Common::String filename) { - SaveStateDescriptor desc; - desc.setDescription(filename); - +SaveStateDescriptor MystGameState::querySaveMetaInfos(int slot) { // Open the metadata file - Common::InSaveFile *metadataFile = openMetadataFile(filename); + Common::String filename = buildMetadataFilename(slot); + Common::InSaveFile *metadataFile = g_system->getSavefileManager()->openForLoading(filename); if (!metadataFile) { - return desc; + return SaveStateDescriptor(); } Common::Serializer m(metadataFile, nullptr); @@ -264,10 +263,11 @@ SaveStateDescriptor MystGameState::querySaveMetaInfos(const Common::String filen Mohawk::MystSaveMetadata metadata; if (!metadata.sync(m)) { delete metadataFile; - return desc; + return SaveStateDescriptor(); } // Set the save description + SaveStateDescriptor desc; desc.setDescription(metadata.saveDescription); desc.setSaveDate(metadata.saveYear, metadata.saveMonth, metadata.saveDay); desc.setSaveTime(metadata.saveHour, metadata.saveMinute); @@ -279,20 +279,26 @@ SaveStateDescriptor MystGameState::querySaveMetaInfos(const Common::String filen return desc; } -Common::InSaveFile *MystGameState::openMetadataFile(const Common::String &filename) { - // Remove the extension - Common::String baseName = removeExtension(filename); - +Common::String MystGameState::querySaveDescription(int slot) { // Open the metadata file - return g_system->getSavefileManager()->openForLoading(baseName + ".mym"); -} + Common::String filename = buildMetadataFilename(slot); + Common::InSaveFile *metadataFile = g_system->getSavefileManager()->openForLoading(filename); + if (!metadataFile) { + return ""; + } + + Common::Serializer m(metadataFile, nullptr); -Common::String MystGameState::removeExtension(const Common::String &filename) { - Common::String baseName = filename; - for (uint i = 0; i < 4; i++) { - baseName.deleteLastChar(); + // Read the metadata file + Mohawk::MystSaveMetadata metadata; + if (!metadata.sync(m)) { + delete metadataFile; + return ""; } - return baseName; + + delete metadataFile; + + return metadata.saveDescription; } void MystGameState::syncGameState(Common::Serializer &s, bool isME) { @@ -471,12 +477,14 @@ void MystGameState::syncGameState(Common::Serializer &s, bool isME) { warning("Unexpected File Position 0x%03X At End of Save/Load", s.bytesSynced()); } -void MystGameState::deleteSave(const Common::String &saveName) { - debugC(kDebugSaveLoad, "Deleting save file \'%s\'", saveName.c_str()); - Common::String basename = removeExtension(saveName); +void MystGameState::deleteSave(int slot) { + Common::String filename = buildSaveFilename(slot); + Common::String metadataFilename = buildMetadataFilename(slot); + + debugC(kDebugSaveLoad, "Deleting save file \'%s\'", filename.c_str()); - g_system->getSavefileManager()->removeSavefile(saveName); - g_system->getSavefileManager()->removeSavefile(basename + ".mym"); + g_system->getSavefileManager()->removeSavefile(filename); + g_system->getSavefileManager()->removeSavefile(metadataFilename); } void MystGameState::addZipDest(uint16 stack, uint16 view) { diff --git a/engines/mohawk/myst_state.h b/engines/mohawk/myst_state.h index 50359a5b52..7d5f3f7102 100644 --- a/engines/mohawk/myst_state.h +++ b/engines/mohawk/myst_state.h @@ -58,12 +58,12 @@ public: MystGameState(MohawkEngine_Myst*, Common::SaveFileManager*); ~MystGameState(); - static Common::StringArray generateSaveGameList(); - static SaveStateDescriptor querySaveMetaInfos(const Common::String filename); + static SaveStateDescriptor querySaveMetaInfos(int slot); + static Common::String querySaveDescription(int slot); - bool load(const Common::String &filename); - bool save(const Common::String &filename); - static void deleteSave(const Common::String &saveName); + bool load(int slot); + bool save(int slot, const Common::String &desc); + static void deleteSave(int slot); void addZipDest(uint16 stack, uint16 view); bool isReachableZipDest(uint16 stack, uint16 view); @@ -292,13 +292,13 @@ public: private: void syncGameState(Common::Serializer &s, bool isME); - static Common::InSaveFile *openMetadataFile(const Common::String &filename); - bool loadState(const Common::String &filename); - void loadMetadata(const Common::String &filename); - bool saveState(const Common::String &desc); + static Common::String buildSaveFilename(int slot); + static Common::String buildMetadataFilename(int slot); + bool loadState(int slot); + void loadMetadata(int slot); + bool saveState(int slot); void updateMetadateForSaving(const Common::String &desc); - bool saveMetadata(const Common::String &desc); - static Common::String removeExtension(const Common::String &filename); + bool saveMetadata(int slot); // The values in these regions are lists of VIEW resources // which correspond to visited zip destinations diff --git a/engines/mohawk/resource.h b/engines/mohawk/resource.h index d9074a5b73..12c5a139e4 100644 --- a/engines/mohawk/resource.h +++ b/engines/mohawk/resource.h @@ -68,6 +68,8 @@ namespace Mohawk { #define ID_VARS MKTAG('V','A','R','S') // Variable Values #define ID_VERS MKTAG('V','E','R','S') // Version Info #define ID_ZIPS MKTAG('Z','I','P','S') // Zip Mode Status +#define ID_META MKTAG('M','E','T','A') // ScummVM save metadata +#define ID_THMB MKTAG('T','H','M','B') // ScummVM save thumbnail // Zoombini Resource FourCC's #define ID_SND MKTAG( 0 ,'S','N','D') // Standard Mohawk Sound diff --git a/engines/mohawk/riven.cpp b/engines/mohawk/riven.cpp index 898f68c581..12b4851a9c 100644 --- a/engines/mohawk/riven.cpp +++ b/engines/mohawk/riven.cpp @@ -25,6 +25,7 @@ #include "common/keyboard.h" #include "common/translation.h" #include "common/system.h" +#include "gui/saveload.h" #include "mohawk/cursors.h" #include "mohawk/installer_archive.h" @@ -33,8 +34,8 @@ #include "mohawk/riven_external.h" #include "mohawk/riven_graphics.h" #include "mohawk/riven_saveload.h" +#include "mohawk/riven_sound.h" #include "mohawk/dialogs.h" -#include "mohawk/sound.h" #include "mohawk/video.h" #include "mohawk/console.h" @@ -54,9 +55,20 @@ MohawkEngine_Riven::MohawkEngine_Riven(OSystem *syst, const MohawkGameDescriptio _gameOver = false; _activatedSLST = false; _ignoreNextMouseUp = false; - _extrasFile = 0; + _extrasFile = nullptr; _curStack = kStackUnknown; - _hotspots = 0; + _hotspots = nullptr; + _gfx = nullptr; + _sound = nullptr; + _externalScriptHandler = nullptr; + _rnd = nullptr; + _scriptMan = nullptr; + _console = nullptr; + _saveLoad = nullptr; + _optionsDialog = nullptr; + _curCard = 0; + _hotspotCount = 0; + _curHotspot = -1; removeTimer(); // NOTE: We can never really support CD swapping. All of the music files @@ -70,6 +82,7 @@ MohawkEngine_Riven::MohawkEngine_Riven(OSystem *syst, const MohawkGameDescriptio SearchMan.addSubDirectoryMatching(gameDataDir, "data"); SearchMan.addSubDirectoryMatching(gameDataDir, "exe"); SearchMan.addSubDirectoryMatching(gameDataDir, "assets1"); + SearchMan.addSubDirectoryMatching(gameDataDir, "program"); g_atrusJournalRect1 = new Common::Rect(295, 402, 313, 426); g_atrusJournalRect2 = new Common::Rect(259, 402, 278, 426); @@ -81,6 +94,7 @@ MohawkEngine_Riven::MohawkEngine_Riven(OSystem *syst, const MohawkGameDescriptio } MohawkEngine_Riven::~MohawkEngine_Riven() { + delete _sound; delete _gfx; delete _console; delete _externalScriptHandler; @@ -112,6 +126,7 @@ Common::Error MohawkEngine_Riven::run() { SearchMan.add("arcriven.z", &_installerArchive, 0, false); _gfx = new RivenGraphics(this); + _sound = new RivenSoundManager(this); _console = new RivenConsole(this); _saveLoad = new RivenSaveLoad(this, _saveFileMan); _externalScriptHandler = new RivenExternal(this); @@ -132,8 +147,8 @@ Common::Error MohawkEngine_Riven::run() { // We need to have a cursor source, or the game won't work if (!_cursor->hasSource()) { - Common::String message = "You're missing a Riven executable. The Windows executable is 'riven.exe' or 'rivendmo.exe'. "; - message += "Using the 'arcriven.z' installer file also works. In addition, you can use the Mac 'Riven' executable."; + Common::String message = _("You're missing a Riven executable. The Windows executable is 'riven.exe' or 'rivendmo.exe'. "); + message += _("Using the 'arcriven.z' installer file also works. In addition, you can use the Mac 'Riven' executable."); GUIErrorMessage(message); warning("%s", message.c_str()); return Common::kNoGameDataFoundError; @@ -144,7 +159,7 @@ Common::Error MohawkEngine_Riven::run() { // We need extras.mhk for inventory images, marble images, and credits images if (!_extrasFile->openFile("extras.mhk")) { - Common::String message = "You're missing 'extras.mhk'. Using the 'arcriven.z' installer file also works."; + Common::String message = _("You're missing 'extras.mhk'. Using the 'arcriven.z' installer file also works."); GUIErrorMessage(message); warning("%s", message.c_str()); return Common::kNoGameDataFoundError; @@ -165,13 +180,10 @@ Common::Error MohawkEngine_Riven::run() { changeToCard(6); } else if (ConfMan.hasKey("save_slot")) { // Load game from launcher/command line if requested - uint32 gameToLoad = ConfMan.getInt("save_slot"); - Common::StringArray savedGamesList = _saveLoad->generateSaveGameList(); - if (gameToLoad > savedGamesList.size()) - error ("Could not find saved game"); + int gameToLoad = ConfMan.getInt("save_slot"); // Attempt to load the game. On failure, just send us to the main menu. - if (_saveLoad->loadGame(savedGamesList[gameToLoad]).getCode() != Common::kNoError) { + if (_saveLoad->loadGame(gameToLoad).getCode() != Common::kNoError) { changeToStack(kStackAspit); changeToCard(1); } @@ -191,6 +203,7 @@ Common::Error MohawkEngine_Riven::run() { void MohawkEngine_Riven::handleEvents() { // Update background running things checkTimer(); + _sound->updateSLST(); bool needsUpdate = _gfx->runScheduledWaterEffects(); needsUpdate |= _video->updateMovies(); @@ -250,6 +263,8 @@ void MohawkEngine_Riven::handleEvents() { break; case Common::KEYCODE_F5: runDialog(*_optionsDialog); + if (_optionsDialog->getLoadSlot() >= 0) + loadGameState(_optionsDialog->getLoadSlot()); updateZipMode(); break; case Common::KEYCODE_r: @@ -700,6 +715,7 @@ void MohawkEngine_Riven::delayAndUpdate(uint32 ms) { uint32 startTime = _system->getMillis(); while (_system->getMillis() < startTime + ms && !shouldQuit()) { + _sound->updateSLST(); bool needsUpdate = _gfx->runScheduledWaterEffects(); needsUpdate |= _video->updateMovies(); @@ -723,16 +739,11 @@ void MohawkEngine_Riven::runLoadDialog() { } Common::Error MohawkEngine_Riven::loadGameState(int slot) { - return _saveLoad->loadGame(_saveLoad->generateSaveGameList()[slot]); + return _saveLoad->loadGame(slot); } Common::Error MohawkEngine_Riven::saveGameState(int slot, const Common::String &desc) { - Common::StringArray saveList = _saveLoad->generateSaveGameList(); - - if ((uint)slot < saveList.size()) - _saveLoad->deleteSave(saveList[slot]); - - return _saveLoad->saveGame(desc); + return _saveLoad->saveGame(slot, desc); } Common::String MohawkEngine_Riven::getStackName(uint16 stack) const { diff --git a/engines/mohawk/riven.h b/engines/mohawk/riven.h index 9c23d07c52..ce819ac970 100644 --- a/engines/mohawk/riven.h +++ b/engines/mohawk/riven.h @@ -27,8 +27,6 @@ #include "mohawk/mohawk.h" #include "mohawk/riven_scripts.h" -#include "gui/saveload.h" - #include "common/hashmap.h" #include "common/hash-str.h" #include "common/random.h" @@ -43,6 +41,7 @@ class RivenExternal; class RivenConsole; class RivenSaveLoad; class RivenOptionsDialog; +class RivenSoundManager; // Riven Stack Types enum { @@ -123,6 +122,7 @@ public: MohawkEngine_Riven(OSystem *syst, const MohawkGameDescription *gamedesc); virtual ~MohawkEngine_Riven(); + RivenSoundManager *_sound; RivenGraphics *_gfx; RivenExternal *_externalScriptHandler; Common::RandomSource *_rnd; diff --git a/engines/mohawk/riven_external.cpp b/engines/mohawk/riven_external.cpp index 00075039fe..fb98145b44 100644 --- a/engines/mohawk/riven_external.cpp +++ b/engines/mohawk/riven_external.cpp @@ -24,12 +24,13 @@ #include "mohawk/riven.h" #include "mohawk/riven_external.h" #include "mohawk/riven_graphics.h" -#include "mohawk/sound.h" +#include "mohawk/riven_sound.h" #include "mohawk/video.h" #include "gui/message.h" #include "common/events.h" #include "common/system.h" +#include "common/translation.h" namespace Mohawk { @@ -209,8 +210,8 @@ void RivenExternal::runCommand(uint16 argc, uint16 *argv) { } void RivenExternal::runDemoBoundaryDialog() { - GUI::MessageDialog dialog("Exploration beyond this point available only within the full version of\n" - "the game."); + GUI::MessageDialog dialog(_("Exploration beyond this point available only within the full version of\n" + "the game.")); dialog.runModal(); } @@ -651,11 +652,11 @@ void RivenExternal::xalaunchbrowser(uint16 argc, uint16 *argv) { // // [YES] [NO] - GUI::MessageDialog dialog("At this point, the Riven Demo would\n" + GUI::MessageDialog dialog(_("At this point, the Riven Demo would\n" "ask if you would like to open a web browser\n" "to bring you to the Red Orb store to buy\n" "the game. ScummVM cannot do that and\n" - "the site no longer exists."); + "the site no longer exists.")); dialog.runModal(); } @@ -2429,7 +2430,7 @@ void RivenExternal::xtexterior300_telescopedown(uint16 argc, uint16 *argv) { // Play the sound of not being able to move _vm->_cursor->setCursor(kRivenHideCursor); _vm->_system->updateScreen(); - _vm->_sound->playSoundBlocking(13); + _vm->_sound->playSound(13); } } else { // We're not at the bottom, and we can move down again @@ -2463,7 +2464,7 @@ void RivenExternal::xtexterior300_telescopeup(uint16 argc, uint16 *argv) { // Play the sound of not being able to move _vm->_cursor->setCursor(kRivenHideCursor); _vm->_system->updateScreen(); - _vm->_sound->playSoundBlocking(13); + _vm->_sound->playSound(13); return; } diff --git a/engines/mohawk/riven_graphics.cpp b/engines/mohawk/riven_graphics.cpp index b44fbb828e..b583bc9710 100644 --- a/engines/mohawk/riven_graphics.cpp +++ b/engines/mohawk/riven_graphics.cpp @@ -23,6 +23,7 @@ #include "mohawk/resource.h" #include "mohawk/riven.h" #include "mohawk/riven_graphics.h" +#include "mohawk/riven_sound.h" #include "common/system.h" #include "engines/util.h" @@ -51,6 +52,8 @@ RivenGraphics::RivenGraphics(MohawkEngine_Riven* vm) : GraphicsManager(), _vm(vm _creditsImage = 302; _creditsPos = 0; + + _transitionSpeed = 0; } RivenGraphics::~RivenGraphics() { @@ -109,6 +112,7 @@ void RivenGraphics::drawPLST(uint16 x) { void RivenGraphics::updateScreen(Common::Rect updateRect) { if (_updatesEnabled) { _vm->runUpdateScreenScript(); + _vm->_sound->triggerDrawSound(); if (_dirtyScreen) { _activatedPLSTs.clear(); diff --git a/engines/mohawk/riven_saveload.cpp b/engines/mohawk/riven_saveload.cpp index 6af66f7a2d..755f87767d 100644 --- a/engines/mohawk/riven_saveload.cpp +++ b/engines/mohawk/riven_saveload.cpp @@ -24,25 +24,141 @@ #include "mohawk/riven.h" #include "mohawk/riven_saveload.h" -#include "common/util.h" +#include "common/system.h" +#include "graphics/thumbnail.h" namespace Mohawk { +RivenSaveMetadata::RivenSaveMetadata() { + saveDay = 0; + saveMonth = 0; + saveYear = 0; + saveHour = 0; + saveMinute = 0; + totalPlayTime = 0; +} + +bool RivenSaveMetadata::sync(Common::Serializer &s) { + static const Common::Serializer::Version kCurrentVersion = 1; + + if (!s.syncVersion(kCurrentVersion)) { + return false; + } + + s.syncAsByte(saveDay); + s.syncAsByte(saveMonth); + s.syncAsUint16BE(saveYear); + s.syncAsByte(saveHour); + s.syncAsByte(saveMinute); + s.syncString(saveDescription); + s.syncAsUint32BE(totalPlayTime); + + return true; +} + RivenSaveLoad::RivenSaveLoad(MohawkEngine_Riven *vm, Common::SaveFileManager *saveFileMan) : _vm(vm), _saveFileMan(saveFileMan) { } RivenSaveLoad::~RivenSaveLoad() { } -Common::StringArray RivenSaveLoad::generateSaveGameList() { - return _saveFileMan->listSavefiles("*.rvn"); +Common::String RivenSaveLoad::buildSaveFilename(const int slot) { + return Common::String::format("riven-%03d.rvn", slot); +} + +Common::String RivenSaveLoad::querySaveDescription(const int slot) { + Common::String filename = buildSaveFilename(slot); + Common::InSaveFile *loadFile = g_system->getSavefileManager()->openForLoading(filename); + if (!loadFile) { + return ""; + } + + MohawkArchive mhk; + if (!mhk.openStream(loadFile)) { + return ""; + } + + if (!mhk.hasResource(ID_META, 1)) { + return ""; + } + + Common::SeekableReadStream *metaStream = mhk.getResource(ID_META, 1); + if (!metaStream) { + return ""; + } + + Common::Serializer serializer = Common::Serializer(metaStream, nullptr); + + RivenSaveMetadata metadata; + if (!metadata.sync(serializer)) { + delete metaStream; + return ""; + } + + delete metaStream; + + return metadata.saveDescription; } -Common::Error RivenSaveLoad::loadGame(Common::String filename) { +SaveStateDescriptor RivenSaveLoad::querySaveMetaInfos(const int slot) { + Common::String filename = buildSaveFilename(slot); + Common::InSaveFile *loadFile = g_system->getSavefileManager()->openForLoading(filename); + if (!loadFile) { + return SaveStateDescriptor(); + } + + MohawkArchive mhk; + if (!mhk.openStream(loadFile)) { + return SaveStateDescriptor(); + } + + if (!mhk.hasResource(ID_META, 1)) { + return SaveStateDescriptor(); + } + + Common::SeekableReadStream *metaStream = mhk.getResource(ID_META, 1); + if (!metaStream) { + return SaveStateDescriptor(); + } + + Common::Serializer serializer = Common::Serializer(metaStream, nullptr); + + RivenSaveMetadata metadata; + if (!metadata.sync(serializer)) { + delete metaStream; + return SaveStateDescriptor(); + } + + SaveStateDescriptor descriptor; + descriptor.setDescription(metadata.saveDescription); + descriptor.setPlayTime(metadata.totalPlayTime); + descriptor.setSaveDate(metadata.saveYear, metadata.saveMonth, metadata.saveDay); + descriptor.setSaveTime(metadata.saveHour, metadata.saveMinute); + + delete metaStream; + + if (!mhk.hasResource(ID_THMB, 1)) { + return descriptor; + } + + Common::SeekableReadStream *thmbStream = mhk.getResource(ID_THMB, 1); + if (!thmbStream) { + return descriptor; + } + + descriptor.setThumbnail(Graphics::loadThumbnail(*thmbStream)); + + delete thmbStream; + + return descriptor; +} + +Common::Error RivenSaveLoad::loadGame(const int slot) { if (_vm->getFeatures() & GF_DEMO) // Don't load games in the demo return Common::kNoError; - Common::InSaveFile *loadFile = _saveFileMan->openForLoading(filename); + Common::String filename = buildSaveFilename(slot); + Common::InSaveFile *loadFile = _saveFileMan->openForLoading(filename); if (!loadFile) return Common::kReadingFailed; @@ -72,8 +188,11 @@ Common::Error RivenSaveLoad::loadGame(Common::String filename) { Common::Array<uint32> rawVariables; while (!vars->eos()) { - vars->readUint32BE(); // Unknown (Stack?) - vars->readUint32BE(); // Unknown (0 or 1) + // The original engine stores the variables values in an array. All the slots in + // the array may not be in use, which is why it needs a reference counter and + // a flag to tell if the value has been set. + vars->readUint32BE(); // Reference counter + vars->readUint32BE(); // Variable initialized flag rawVariables.push_back(vars->readUint32BE()); } @@ -144,6 +263,20 @@ Common::Error RivenSaveLoad::loadGame(Common::String filename) { } delete zips; + + // Load the ScummVM specific save metadata + if (mhk->hasResource(ID_META, 1)) { + Common::SeekableReadStream *metadataStream = mhk->getResource(ID_META, 1); + Common::Serializer serializer = Common::Serializer(metadataStream, nullptr); + + RivenSaveMetadata metadata; + metadata.sync(serializer); + + // Set the saved total play time + _vm->setTotalPlayTime(metadata.totalPlayTime); + + delete metadataStream; + } delete mhk; return Common::kNoError; @@ -162,14 +295,18 @@ Common::MemoryWriteStreamDynamic *RivenSaveLoad::genVARSSection() { Common::MemoryWriteStreamDynamic *stream = new Common::MemoryWriteStreamDynamic(); for (RivenVariableMap::const_iterator it = _vm->_vars.begin(); it != _vm->_vars.end(); it++) { - stream->writeUint32BE(0); // Unknown - stream->writeUint32BE(0); // Unknown + stream->writeUint32BE(1); // Reference counter + stream->writeUint32BE(1); // Variable initialized flag stream->writeUint32BE(it->_value); } return stream; } +static int stringCompareToIgnoreCase(const Common::String &s1, const Common::String &s2) { + return s1.compareToIgnoreCase(s2) < 0; +} + Common::MemoryWriteStreamDynamic *RivenSaveLoad::genNAMESection() { Common::MemoryWriteStreamDynamic *stream = new Common::MemoryWriteStreamDynamic(); @@ -181,8 +318,28 @@ Common::MemoryWriteStreamDynamic *RivenSaveLoad::genNAMESection() { curPos += it->_key.size() + 1; } - for (uint16 i = 0; i < _vm->_vars.size(); i++) - stream->writeUint16BE(i); + // The original engine does not store the variables in a HashMap, but in a "NameList" + // for the keys and an array for the values. The NameList data structure maintains an array + // of indices in the string table sorted by case insensitive key alphabetical order. + // It is used to perform fast key -> index lookups. + // ScummVM does not need the sorted array, but has to write it anyway for the saved games + // to be compatible with original engine. + Common::Array<Common::String> sortedKeys; + for (RivenVariableMap::const_iterator it = _vm->_vars.begin(); it != _vm->_vars.end(); it++) { + sortedKeys.push_back(it->_key); + } + Common::sort(sortedKeys.begin(), sortedKeys.end(), stringCompareToIgnoreCase); + + for (uint i = 0; i < sortedKeys.size(); i++) { + uint16 varIndex = 0; + for (RivenVariableMap::const_iterator it = _vm->_vars.begin(); it != _vm->_vars.end(); it++) { + if (it->_key == sortedKeys[i]) { + stream->writeUint16BE(varIndex); + break; + } + varIndex++; + } + } for (RivenVariableMap::const_iterator it = _vm->_vars.begin(); it != _vm->_vars.end(); it++) { stream->write(it->_key.c_str(), it->_key.size()); @@ -206,7 +363,35 @@ Common::MemoryWriteStreamDynamic *RivenSaveLoad::genZIPSSection() { return stream; } -Common::Error RivenSaveLoad::saveGame(Common::String filename) { +Common::MemoryWriteStreamDynamic *RivenSaveLoad::genTHMBSection() const { + Common::MemoryWriteStreamDynamic *stream = new Common::MemoryWriteStreamDynamic(); + + Graphics::saveThumbnail(*stream); + + return stream; +} + +Common::MemoryWriteStreamDynamic *RivenSaveLoad::genMETASection(const Common::String &desc) const { + Common::MemoryWriteStreamDynamic *stream = new Common::MemoryWriteStreamDynamic(); + Common::Serializer serializer = Common::Serializer(nullptr, stream); + + TimeDate t; + _vm->_system->getTimeAndDate(t); + + RivenSaveMetadata metadata; + metadata.saveDay = t.tm_mday; + metadata.saveMonth = t.tm_mon + 1; + metadata.saveYear = t.tm_year + 1900; + metadata.saveHour = t.tm_hour; + metadata.saveMinute = t.tm_min; + metadata.saveDescription = desc; + metadata.totalPlayTime = _vm->getTotalPlayTime(); + metadata.sync(serializer); + + return stream; +} + +Common::Error RivenSaveLoad::saveGame(const int slot, const Common::String &description) { // NOTE: This code is designed to only output a Mohawk archive // for a Riven saved game. It's hardcoded to do this because // (as of right now) this is the only place in the engine @@ -214,13 +399,7 @@ Common::Error RivenSaveLoad::saveGame(Common::String filename) { // games need this, we should think about coming up with some // more common way of outputting resources to an archive. - // TODO: Make these saves work with the original interpreter. - // Not sure why they don't work yet (they still can be loaded - // by ScummVM). - - // Make sure we have the right extension - if (!filename.matchString("*.rvn", true)) - filename += ".rvn"; + Common::String filename = buildSaveFilename(slot); // Convert class variables to variable numbers _vm->_vars["currentstackid"] = _vm->getCurStack(); @@ -232,16 +411,20 @@ Common::Error RivenSaveLoad::saveGame(Common::String filename) { debug (0, "Saving game to \'%s\'", filename.c_str()); - Common::MemoryWriteStreamDynamic *versSection = genVERSSection(); + Common::MemoryWriteStreamDynamic *metaSection = genMETASection(description); Common::MemoryWriteStreamDynamic *nameSection = genNAMESection(); + Common::MemoryWriteStreamDynamic *thmbSection = genTHMBSection(); Common::MemoryWriteStreamDynamic *varsSection = genVARSSection(); + Common::MemoryWriteStreamDynamic *versSection = genVERSSection(); Common::MemoryWriteStreamDynamic *zipsSection = genZIPSSection(); // Let's calculate the file size! - uint32 fileSize = 142; - fileSize += versSection->size(); + uint32 fileSize = 194; + fileSize += metaSection->size(); fileSize += nameSection->size(); + fileSize += thmbSection->size(); fileSize += varsSection->size(); + fileSize += versSection->size(); fileSize += zipsSection->size(); // MHWK Header (8 bytes - total: 8) @@ -254,109 +437,151 @@ Common::Error RivenSaveLoad::saveGame(Common::String filename) { saveFile->writeUint16BE(1); // Compaction -- original saves have this too saveFile->writeUint32BE(fileSize); // Subtract off the MHWK header size saveFile->writeUint32BE(28); // Absolute offset: right after both headers - saveFile->writeUint16BE(70); // File Table Offset - saveFile->writeUint16BE(44); // File Table Size (4 bytes count + 4 entries * 10 bytes per entry) + saveFile->writeUint16BE(102); // File Table Offset + saveFile->writeUint16BE(64); // File Table Size (4 bytes count + 6 entries * 10 bytes per entry) // Type Table (4 bytes - total: 32) - saveFile->writeUint16BE(36); // String table offset After the Type Table Entries - saveFile->writeUint16BE(4); // 4 Type Table Entries + saveFile->writeUint16BE(52); // String table offset After the Type Table Entries + saveFile->writeUint16BE(6); // 6 Type Table Entries - // Hardcode Entries (32 bytes - total: 64) - saveFile->writeUint32BE(ID_VERS); - saveFile->writeUint16BE(46); // Resource table offset - saveFile->writeUint16BE(38); // String table offset + // Hardcode Entries (48 bytes - total: 80) + // The original engine relies on the entries being sorted by tag alphabetical order + // to optimize its lookup algorithm. + saveFile->writeUint32BE(ID_META); + saveFile->writeUint16BE(66); // Resource table offset + saveFile->writeUint16BE(54); // String table offset saveFile->writeUint32BE(ID_NAME); - saveFile->writeUint16BE(52); - saveFile->writeUint16BE(40); + saveFile->writeUint16BE(72); + saveFile->writeUint16BE(56); - saveFile->writeUint32BE(ID_VARS); + saveFile->writeUint32BE(ID_THMB); + saveFile->writeUint16BE(78); saveFile->writeUint16BE(58); - saveFile->writeUint16BE(42); + + saveFile->writeUint32BE(ID_VARS); + saveFile->writeUint16BE(84); + saveFile->writeUint16BE(60); + + saveFile->writeUint32BE(ID_VERS); + saveFile->writeUint16BE(90); + saveFile->writeUint16BE(62); saveFile->writeUint32BE(ID_ZIPS); + saveFile->writeUint16BE(96); saveFile->writeUint16BE(64); - saveFile->writeUint16BE(44); - // Pseudo-String Table (2 bytes - total: 66) + // Pseudo-String Table (2 bytes - total: 82) saveFile->writeUint16BE(0); // We don't need a name list - // Psuedo-Name Tables (8 bytes - total: 74) + // Pseudo-Name Tables (12 bytes - total: 94) + saveFile->writeUint16BE(0); + saveFile->writeUint16BE(0); saveFile->writeUint16BE(0); saveFile->writeUint16BE(0); saveFile->writeUint16BE(0); saveFile->writeUint16BE(0); - // VERS Section (Resource Table) (6 bytes - total: 80) + // META Section (Resource Table) (6 bytes - total: 100) saveFile->writeUint16BE(1); saveFile->writeUint16BE(1); saveFile->writeUint16BE(1); - // NAME Section (Resource Table) (6 bytes - total: 86) + // NAME Section (Resource Table) (6 bytes - total: 106) saveFile->writeUint16BE(1); saveFile->writeUint16BE(1); saveFile->writeUint16BE(2); - // VARS Section (Resource Table) (6 bytes - total: 92) + // THMB Section (Resource Table) (6 bytes - total: 112) saveFile->writeUint16BE(1); saveFile->writeUint16BE(1); saveFile->writeUint16BE(3); - // ZIPS Section (Resource Table) (6 bytes - total: 98) + // VARS Section (Resource Table) (6 bytes - total: 118) saveFile->writeUint16BE(1); saveFile->writeUint16BE(1); saveFile->writeUint16BE(4); - // File Table (4 bytes - total: 102) - saveFile->writeUint32BE(4); + // VERS Section (Resource Table) (6 bytes - total: 124) + saveFile->writeUint16BE(1); + saveFile->writeUint16BE(1); + saveFile->writeUint16BE(5); - // VERS Section (File Table) (10 bytes - total: 112) - saveFile->writeUint32BE(142); - saveFile->writeUint16BE(versSection->size() & 0xFFFF); - saveFile->writeByte((versSection->size() & 0xFF0000) >> 16); + // ZIPS Section (Resource Table) (6 bytes - total: 130) + saveFile->writeUint16BE(1); + saveFile->writeUint16BE(1); + saveFile->writeUint16BE(6); + + // File Table (4 bytes - total: 134) + saveFile->writeUint32BE(6); + + // META Section (File Table) (10 bytes - total: 144) + saveFile->writeUint32BE(194); + saveFile->writeUint16BE(metaSection->size() & 0xFFFF); + saveFile->writeByte((metaSection->size() & 0xFF0000) >> 16); saveFile->writeByte(0); saveFile->writeUint16BE(0); - // NAME Section (File Table) (10 bytes - total: 122) - saveFile->writeUint32BE(142 + versSection->size()); + // NAME Section (File Table) (10 bytes - total: 154) + saveFile->writeUint32BE(194 + metaSection->size()); saveFile->writeUint16BE(nameSection->size() & 0xFFFF); saveFile->writeByte((nameSection->size() & 0xFF0000) >> 16); saveFile->writeByte(0); saveFile->writeUint16BE(0); - // VARS Section (File Table) (10 bytes - total: 132) - saveFile->writeUint32BE(142 + versSection->size() + nameSection->size()); + // THMB Section (File Table) (10 bytes - total: 164) + saveFile->writeUint32BE(194 + metaSection->size() + nameSection->size()); + saveFile->writeUint16BE(thmbSection->size() & 0xFFFF); + saveFile->writeByte((thmbSection->size() & 0xFF0000) >> 16); + saveFile->writeByte(0); + saveFile->writeUint16BE(0); + + // VARS Section (File Table) (10 bytes - total: 174) + saveFile->writeUint32BE(194 + metaSection->size() + nameSection->size() + thmbSection->size()); saveFile->writeUint16BE(varsSection->size() & 0xFFFF); saveFile->writeByte((varsSection->size() & 0xFF0000) >> 16); saveFile->writeByte(0); saveFile->writeUint16BE(0); - // ZIPS Section (File Table) (10 bytes - total: 142) - saveFile->writeUint32BE(142 + versSection->size() + nameSection->size() + varsSection->size()); + // VERS Section (File Table) (10 bytes - total: 184) + saveFile->writeUint32BE(194 + metaSection->size() + nameSection->size() + thmbSection->size() + varsSection->size()); + saveFile->writeUint16BE(versSection->size() & 0xFFFF); + saveFile->writeByte((versSection->size() & 0xFF0000) >> 16); + saveFile->writeByte(0); + saveFile->writeUint16BE(0); + + // ZIPS Section (File Table) (10 bytes - total: 194) + saveFile->writeUint32BE(194 + metaSection->size() + nameSection->size() + thmbSection->size() + varsSection->size() + versSection->size()); saveFile->writeUint16BE(zipsSection->size() & 0xFFFF); saveFile->writeByte((zipsSection->size() & 0xFF0000) >> 16); saveFile->writeByte(0); saveFile->writeUint16BE(0); - saveFile->write(versSection->getData(), versSection->size()); + saveFile->write(metaSection->getData(), metaSection->size()); saveFile->write(nameSection->getData(), nameSection->size()); + saveFile->write(thmbSection->getData(), thmbSection->size()); saveFile->write(varsSection->getData(), varsSection->size()); + saveFile->write(versSection->getData(), versSection->size()); saveFile->write(zipsSection->getData(), zipsSection->size()); saveFile->finalize(); delete saveFile; - delete versSection; + delete metaSection; delete nameSection; + delete thmbSection; delete varsSection; + delete versSection; delete zipsSection; return Common::kNoError; } -void RivenSaveLoad::deleteSave(Common::String saveName) { - debug (0, "Deleting save file \'%s\'", saveName.c_str()); - _saveFileMan->removeSavefile(saveName); +void RivenSaveLoad::deleteSave(const int slot) { + Common::String filename = buildSaveFilename(slot); + + debug (0, "Deleting save file \'%s\'", filename.c_str()); + g_system->getSavefileManager()->removeSavefile(filename); } } // End of namespace Mohawk diff --git a/engines/mohawk/riven_saveload.h b/engines/mohawk/riven_saveload.h index a6ddce5713..34bfbdc434 100644 --- a/engines/mohawk/riven_saveload.h +++ b/engines/mohawk/riven_saveload.h @@ -23,10 +23,13 @@ #ifndef MOHAWK_SAVELOAD_H #define MOHAWK_SAVELOAD_H +#include "common/serializer.h" #include "common/savefile.h" #include "common/str.h" #include "common/memstream.h" +#include "engines/savestate.h" + namespace Mohawk { class MohawkEngine_Riven; @@ -36,23 +39,45 @@ enum { kDVDSaveGameVersion = 0x00010100 }; +struct RivenSaveMetadata { + uint8 saveDay; + uint8 saveMonth; + uint16 saveYear; + + uint8 saveHour; + uint8 saveMinute; + + uint32 totalPlayTime; + + Common::String saveDescription; + + RivenSaveMetadata(); + bool sync(Common::Serializer &s); +}; + class RivenSaveLoad { public: RivenSaveLoad(MohawkEngine_Riven*, Common::SaveFileManager*); ~RivenSaveLoad(); - Common::StringArray generateSaveGameList(); - Common::Error loadGame(Common::String); - Common::Error saveGame(Common::String); - void deleteSave(Common::String); + Common::Error loadGame(const int slot); + Common::Error saveGame(const int slot, const Common::String &description); + static void deleteSave(const int slot); + + static SaveStateDescriptor querySaveMetaInfos(const int slot); + static Common::String querySaveDescription(const int slot); private: MohawkEngine_Riven *_vm; Common::SaveFileManager *_saveFileMan; - Common::MemoryWriteStreamDynamic *genVERSSection(); + static Common::String buildSaveFilename(const int slot); + Common::MemoryWriteStreamDynamic *genNAMESection(); + Common::MemoryWriteStreamDynamic *genMETASection(const Common::String &desc) const; + Common::MemoryWriteStreamDynamic *genTHMBSection() const; Common::MemoryWriteStreamDynamic *genVARSSection(); + Common::MemoryWriteStreamDynamic *genVERSSection(); Common::MemoryWriteStreamDynamic *genZIPSSection(); }; diff --git a/engines/mohawk/riven_scripts.cpp b/engines/mohawk/riven_scripts.cpp index caa235ec8b..3655452603 100644 --- a/engines/mohawk/riven_scripts.cpp +++ b/engines/mohawk/riven_scripts.cpp @@ -25,7 +25,7 @@ #include "mohawk/riven_external.h" #include "mohawk/riven_graphics.h" #include "mohawk/riven_scripts.h" -#include "mohawk/sound.h" +#include "mohawk/riven_sound.h" #include "mohawk/video.h" #include "common/memstream.h" @@ -309,54 +309,44 @@ void RivenScript::switchCard(uint16 op, uint16 argc, uint16 *argv) { // Command 3: play an SLST from the script void RivenScript::playScriptSLST(uint16 op, uint16 argc, uint16 *argv) { - SLSTRecord slstRecord; int offset = 0, j = 0; + uint16 soundCount = argv[offset++]; + SLSTRecord slstRecord; slstRecord.index = 0; // not set by the scripts, so we set it to 0 - slstRecord.sound_count = argv[0]; - slstRecord.sound_ids = new uint16[slstRecord.sound_count]; - - offset = slstRecord.sound_count; + slstRecord.soundIds.resize(soundCount); - for (j = 0; j < slstRecord.sound_count; j++) - slstRecord.sound_ids[j] = argv[offset++]; - slstRecord.fade_flags = argv[offset++]; + for (j = 0; j < soundCount; j++) + slstRecord.soundIds[j] = argv[offset++]; + slstRecord.fadeFlags = argv[offset++]; slstRecord.loop = argv[offset++]; - slstRecord.global_volume = argv[offset++]; + slstRecord.globalVolume = argv[offset++]; slstRecord.u0 = argv[offset++]; - slstRecord.u1 = argv[offset++]; + slstRecord.suspend = argv[offset++]; - slstRecord.volumes = new uint16[slstRecord.sound_count]; - slstRecord.balances = new int16[slstRecord.sound_count]; - slstRecord.u2 = new uint16[slstRecord.sound_count]; + slstRecord.volumes.resize(soundCount); + slstRecord.balances.resize(soundCount); + slstRecord.u2.resize(soundCount); - for (j = 0; j < slstRecord.sound_count; j++) + for (j = 0; j < soundCount; j++) slstRecord.volumes[j] = argv[offset++]; - for (j = 0; j < slstRecord.sound_count; j++) + for (j = 0; j < soundCount; j++) slstRecord.balances[j] = argv[offset++]; // negative = left, 0 = center, positive = right - for (j = 0; j < slstRecord.sound_count; j++) + for (j = 0; j < soundCount; j++) slstRecord.u2[j] = argv[offset++]; // Unknown // Play the requested sound list _vm->_sound->playSLST(slstRecord); - _vm->_activatedSLST = true; - - delete[] slstRecord.sound_ids; - delete[] slstRecord.volumes; - delete[] slstRecord.balances; - delete[] slstRecord.u2; } // Command 4: play local tWAV resource (twav_id, volume, block) void RivenScript::playSound(uint16 op, uint16 argc, uint16 *argv) { - byte volume = Sound::convertRivenVolume(argv[1]); + uint16 volume = argv[1]; + bool playOnDraw = argv[2] == 1; - if (argv[2] == 1) - _vm->_sound->playSoundBlocking(argv[0], volume); - else - _vm->_sound->playSound(argv[0], volume); + _vm->_sound->playSound(argv[0], volume, playOnDraw); } // Command 7: set variable value (variable, value) diff --git a/engines/mohawk/riven_sound.cpp b/engines/mohawk/riven_sound.cpp new file mode 100644 index 0000000000..10a23a0719 --- /dev/null +++ b/engines/mohawk/riven_sound.cpp @@ -0,0 +1,459 @@ +/* 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 "common/debug.h" +#include "common/system.h" + +#include "audio/audiostream.h" + +#include "mohawk/riven_sound.h" +#include "mohawk/sound.h" + +namespace Mohawk { + +RivenSoundManager::RivenSoundManager(MohawkEngine *vm) : + _vm(vm), + _effect(nullptr), + _mainAmbientSoundId(-1), + _effectPlayOnDraw(false), + _nextFadeUpdate(0) { + +} + +RivenSoundManager::~RivenSoundManager() { + stopSound(); + stopAllSLST(false); +} + +Audio::RewindableAudioStream *RivenSoundManager::makeAudioStream(uint16 id) { + return makeMohawkWaveStream(_vm->getResource(ID_TWAV, id)); +} + +void RivenSoundManager::playSound(uint16 id, uint16 volume, bool playOnDraw) { + debug (0, "Playing sound %d", id); + + stopSound(); + + Audio::RewindableAudioStream *rewindStream = makeAudioStream(id); + if (!rewindStream) { + warning("Unable to play sound with id %d", id); + return; + } + + _effect = new RivenSound(_vm, rewindStream); + _effect->setVolume(volume); + + _effectPlayOnDraw = playOnDraw; + if (!playOnDraw) { + _effect->play(); + } +} + +void RivenSoundManager::playSLST(uint16 index, uint16 card) { + Common::SeekableReadStream *slstStream = _vm->getResource(ID_SLST, card); + + uint16 recordCount = slstStream->readUint16BE(); + + for (uint16 i = 0; i < recordCount; i++) { + SLSTRecord slstRecord; + slstRecord.index = slstStream->readUint16BE(); + + uint16 soundCount = slstStream->readUint16BE(); + slstRecord.soundIds.resize(soundCount); + + for (uint16 j = 0; j < soundCount; j++) + slstRecord.soundIds[j] = slstStream->readUint16BE(); + + slstRecord.fadeFlags = slstStream->readUint16BE(); + slstRecord.loop = slstStream->readUint16BE(); + slstRecord.globalVolume = slstStream->readUint16BE(); + slstRecord.u0 = slstStream->readUint16BE(); // Unknown + + if (slstRecord.u0 > 1) + warning("slstRecord.u0: %d non-boolean", slstRecord.u0); + + slstRecord.suspend = slstStream->readUint16BE(); + + if (slstRecord.suspend != 0) + warning("slstRecord.u1: %d non-zero", slstRecord.suspend); + + slstRecord.volumes.resize(soundCount); + slstRecord.balances.resize(soundCount); + slstRecord.u2.resize(soundCount); + + for (uint16 j = 0; j < soundCount; j++) + slstRecord.volumes[j] = slstStream->readUint16BE(); + + for (uint16 j = 0; j < soundCount; j++) + slstRecord.balances[j] = slstStream->readSint16BE(); // negative = left, 0 = center, positive = right + + for (uint16 j = 0; j < soundCount; j++) { + slstRecord.u2[j] = slstStream->readUint16BE(); // Unknown + + if (slstRecord.u2[j] != 255 && slstRecord.u2[j] != 256) + warning("slstRecord.u2[%d]: %d not 255 or 256", j, slstRecord.u2[j]); + } + + if (slstRecord.index == index) { + playSLST(slstRecord); + delete slstStream; + return; + } + } + + delete slstStream; + + // If we have no matching entries, we do nothing and just let + // the previous ambient sounds continue. +} + +void RivenSoundManager::playSLST(const SLSTRecord &slstRecord) { + if (slstRecord.soundIds.empty()) { + return; + } + + if (slstRecord.soundIds[0] == _mainAmbientSoundId) { + if (slstRecord.soundIds.size() > _ambientSounds.sounds.size()) { + addAmbientSounds(slstRecord); + } + setAmbientLooping(slstRecord.loop); + setTargetVolumes(slstRecord); + _ambientSounds.suspend = slstRecord.suspend; + if (slstRecord.suspend) { + freePreviousAmbientSounds(); + pauseAmbientSounds(); + applyTargetVolumes(); + } else { + playAmbientSounds(); + } + } else { + _mainAmbientSoundId = slstRecord.soundIds[0]; + freePreviousAmbientSounds(); + moveAmbientSoundsToPreviousSounds(); + addAmbientSounds(slstRecord); + setAmbientLooping(slstRecord.loop); + setTargetVolumes(slstRecord); + _ambientSounds.suspend = slstRecord.suspend; + if (slstRecord.suspend) { + freePreviousAmbientSounds(); + applyTargetVolumes(); + } else { + startFadingAmbientSounds(slstRecord.fadeFlags); + } + } +} + +void RivenSoundManager::stopAllSLST(bool fade) { + _mainAmbientSoundId = -1; + freePreviousAmbientSounds(); + moveAmbientSoundsToPreviousSounds(); + startFadingAmbientSounds(fade ? kFadeOutPreviousSounds : 0); +} + +void RivenSoundManager::stopSound() { + if (_effect) { + delete _effect; + } + _effect = nullptr; + _effectPlayOnDraw = false; +} + +void RivenSoundManager::addAmbientSounds(const SLSTRecord &record) { + if (record.soundIds.size() > _ambientSounds.sounds.size()) { + uint oldSize = _ambientSounds.sounds.size(); + + // Resize the list to the new size + _ambientSounds.sounds.resize(record.soundIds.size()); + + // Add new elements to the list + for (uint i = oldSize; i < _ambientSounds.sounds.size(); i++) { + Audio::RewindableAudioStream *stream = makeAudioStream(record.soundIds[i]); + + RivenSound *sound = new RivenSound(_vm, stream); + sound->setVolume(record.volumes[i]); + sound->setBalance(record.balances[i]); + + _ambientSounds.sounds[i].sound = sound; + _ambientSounds.sounds[i].targetVolume = record.volumes[i]; + _ambientSounds.sounds[i].targetBalance = record.balances[i]; + } + } +} + +void RivenSoundManager::setTargetVolumes(const SLSTRecord &record) { + for (uint i = 0; i < record.volumes.size(); i++) { + _ambientSounds.sounds[i].targetVolume = record.volumes[i] * record.globalVolume / 256; + _ambientSounds.sounds[i].targetBalance = record.balances[i]; + } + _ambientSounds.fading = true; +} + +void RivenSoundManager::freePreviousAmbientSounds() { + for (uint i = 0; i < _previousAmbientSounds.sounds.size(); i++) { + delete _previousAmbientSounds.sounds[i].sound; + } + _previousAmbientSounds = AmbientSoundList(); +} + +void RivenSoundManager::moveAmbientSoundsToPreviousSounds() { + _previousAmbientSounds = _ambientSounds; + _ambientSounds = AmbientSoundList(); +} + +void RivenSoundManager::applyTargetVolumes() { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + AmbientSound &ambientSound = _ambientSounds.sounds[i]; + RivenSound *sound = ambientSound.sound; + sound->setVolume(ambientSound.targetVolume); + sound->setBalance(ambientSound.targetBalance); + } + _ambientSounds.fading = false; +} + +void RivenSoundManager::startFadingAmbientSounds(uint16 flags) { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + AmbientSound &ambientSound = _ambientSounds.sounds[i]; + uint16 volume; + if (flags & kFadeInNewSounds) { + volume = 0; + } else { + volume = ambientSound.targetVolume; + } + ambientSound.sound->setVolume(volume); + } + _ambientSounds.fading = true; + playAmbientSounds(); + + if (!_previousAmbientSounds.sounds.empty()) { + if (flags) { + _previousAmbientSounds.fading = true; + } else { + freePreviousAmbientSounds(); + } + + for (uint i = 0; i < _previousAmbientSounds.sounds.size(); i++) { + AmbientSound &ambientSound = _previousAmbientSounds.sounds[i]; + if (flags & kFadeOutPreviousSounds) { + ambientSound.targetVolume = 0; + } else { + ambientSound.sound->setVolume(ambientSound.targetVolume); + } + } + } +} + +void RivenSoundManager::playAmbientSounds() { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + _ambientSounds.sounds[i].sound->play(); + } +} + +void RivenSoundManager::setAmbientLooping(bool loop) { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + _ambientSounds.sounds[i].sound->setLooping(loop); + } +} + +void RivenSoundManager::triggerDrawSound() { + if (_effectPlayOnDraw && _effect) { + _effect->play(); + } + _effectPlayOnDraw = false; +} + +void RivenSoundManager::pauseAmbientSounds() { + for (uint i = 0; i < _ambientSounds.sounds.size(); i++) { + _ambientSounds.sounds[i].sound->pause(); + } +} + +void RivenSoundManager::updateSLST() { + uint32 time = _vm->_system->getMillis(); + int32 delta = CLIP<int32>(time - _nextFadeUpdate, -50, 50); + if (_nextFadeUpdate == 0 || delta > 0) { + _nextFadeUpdate = time + 50 - delta; + + if (_ambientSounds.fading) { + fadeAmbientSoundList(_ambientSounds); + } + + if (_previousAmbientSounds.fading) { + fadeAmbientSoundList(_previousAmbientSounds); + } + + if (!_previousAmbientSounds.sounds.empty() && !_ambientSounds.fading && !_previousAmbientSounds.fading) { + freePreviousAmbientSounds(); + } + } +} + +void RivenSoundManager::fadeAmbientSoundList(AmbientSoundList &list) { + list.fading = false; + + for (uint i = 0; i < list.sounds.size(); i++) { + AmbientSound &ambientSound = list.sounds[i]; + list.fading |= fadeVolume(ambientSound); + list.fading |= fadeBalance(ambientSound); + } +} + +bool RivenSoundManager::fadeVolume(AmbientSound &ambientSound) { + uint16 volume = ambientSound.sound->getVolume(); + float delta = (ambientSound.targetVolume - volume) / 30.0f; + + if (ABS<float>(delta) < 0.01f) { + ambientSound.sound->setVolume(ambientSound.targetVolume); + return false; + } else { + // Make sure the increment is not zero once converted to an integer + if (delta > 0 && delta < 1) { + delta = 1; + } else if (delta < 0 && delta > -1) { + delta = -1; + } + + ambientSound.sound->setVolume(volume + delta); + return true; + } +} + +bool RivenSoundManager::fadeBalance(RivenSoundManager::AmbientSound &ambientSound) { + int16 balance = ambientSound.sound->getBalance(); + float delta = (ambientSound.targetBalance - balance) / 10.0f; + + if (ABS<float>(delta) < 0.01) { + ambientSound.sound->setBalance(ambientSound.targetBalance); + return false; + } else { + // Make sure the increment is not zero once converted to an integer + if (delta > 0 && delta < 1) { + delta = 1; + } else if (delta < 0 && delta > -1) { + delta = -1; + } + + ambientSound.sound->setBalance(balance + delta); + return true; + } +} + +RivenSound::RivenSound(MohawkEngine *vm, Audio::RewindableAudioStream *rewindStream) : + _vm(vm), + _volume(Audio::Mixer::kMaxChannelVolume), + _balance(0), + _looping(false), + _stream(rewindStream) { + +} + +bool RivenSound::isPlaying() const { + return _vm->_mixer->isSoundHandleActive(_handle); +} + +void RivenSound::pause() { + _vm->_mixer->pauseHandle(_handle, true); +} + +void RivenSound::setVolume(uint16 volume) { + _volume = volume; + if (isPlaying()) { + byte mixerVolume = convertVolume(volume); + _vm->_mixer->setChannelVolume(_handle, mixerVolume); + } +} + +void RivenSound::setBalance(int16 balance) { + _balance = balance; + if (isPlaying()) { + int8 mixerBalance = convertBalance(balance); + _vm->_mixer->setChannelBalance(_handle, mixerBalance); + } +} + +void RivenSound::setLooping(bool loop) { + if (isPlaying() && _looping != loop) { + warning("Changing loop state while a sound is playing is not implemented."); + } + _looping = loop; +} + +void RivenSound::play() { + if (isPlaying()) { + // If the sound is already playing, make sure it is not paused + _vm->_mixer->pauseHandle(_handle, false); + return; + } + + if (!_stream) { + warning("Trying to play a sound without a stream"); + return; + } + + Audio::AudioStream *playStream; + if (_looping) { + playStream = new Audio::LoopingAudioStream(_stream, 0); + } else { + playStream = _stream; + } + + int8 mixerBalance = convertBalance(_balance); + byte mixerVolume = convertVolume(_volume); + _vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, playStream, -1, mixerVolume, mixerBalance); + _stream = nullptr; +} + +byte RivenSound::convertVolume(uint16 volume) { + // The volume is a fixed point value in the Mohawk part of the original engine. + // It's not clear what happens when it is higher than one. + return (volume > 255) ? 255 : volume; +} + +int8 RivenSound::convertBalance(int16 balance) { + return (int8)(balance >> 8); +} + +RivenSound::~RivenSound() { + _vm->_mixer->stopHandle(_handle); + delete _stream; +} + +int16 RivenSound::getBalance() const { + return _balance; +} + +uint16 RivenSound::getVolume() const { + return _volume; +} + +RivenSoundManager::AmbientSound::AmbientSound() : + sound(nullptr), + targetVolume(0), + targetBalance(0) { + +} + +RivenSoundManager::AmbientSoundList::AmbientSoundList() : + fading(false), + suspend(false) { +} + +} // End of namespace Mohawk diff --git a/engines/mohawk/riven_sound.h b/engines/mohawk/riven_sound.h new file mode 100644 index 0000000000..c79ccc3e3a --- /dev/null +++ b/engines/mohawk/riven_sound.h @@ -0,0 +1,197 @@ +/* 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 MOHAWK_RIVEN_SOUND_H +#define MOHAWK_RIVEN_SOUND_H + +#include "common/array.h" +#include "common/str.h" + +#include "audio/mixer.h" + +namespace Audio { +class RewindableAudioStream; +} + +namespace Mohawk { + +class MohawkEngine; +class RivenSound; + +/** + * Ambient sound list + */ +struct SLSTRecord { + uint16 index; + Common::Array<uint16> soundIds; + uint16 fadeFlags; + uint16 loop; + uint16 globalVolume; + uint16 u0; + uint16 suspend; + Common::Array<uint16> volumes; + Common::Array<int16> balances; + Common::Array<uint16> u2; +}; + +/** + * Sound manager for Riven + * + * The sound manager can play simulteaneously: + * - An effect sound + * - A list of ambient sounds + * + * The list of ambient sounds can be cross faded + * with the previously running ambient sounds. + */ +class RivenSoundManager { +public: + RivenSoundManager(MohawkEngine *vm); + ~RivenSoundManager(); + + /** + * Play an effect sound + * + * @param id Sound ID in the stack + * @param volume Playback volume, between 0 and 255 + * @param playOnDraw Start playing when the current card is drawn instead of immediatly + */ + void playSound(uint16 id, uint16 volume = 255, bool playOnDraw = false); + + /** Start playing the scheduled on-draw effect sound, if any. Called by the GraphicsManager. */ + void triggerDrawSound(); + + /** Stop playing the current effect sound, if any */ + void stopSound(); + + /** Start playing an ambient sound list */ + void playSLST(const SLSTRecord &slstRecord); + + /** Start playing an ambient sound list from a resource */ + void playSLST(uint16 index, uint16 card); + + /** Stop playing the current ambient sounds */ + void stopAllSLST(bool fade = false); + + /** Update the ambient sounds for fading. Called once per frame. */ + void updateSLST(); + +private: + struct AmbientSound { + RivenSound *sound; + uint16 targetVolume; + int16 targetBalance; + + AmbientSound(); + }; + + struct AmbientSoundList { + bool fading; + bool suspend; + Common::Array<AmbientSound> sounds; + + AmbientSoundList(); + }; + + enum FadeFlags { + kFadeOutPreviousSounds = 1, + kFadeInNewSounds = 2 + }; + + MohawkEngine *_vm; + + int16 _mainAmbientSoundId; + AmbientSoundList _ambientSounds; + AmbientSoundList _previousAmbientSounds; + uint32 _nextFadeUpdate; + + RivenSound *_effect; + bool _effectPlayOnDraw; + + Audio::RewindableAudioStream *makeAudioStream(uint16 id); + + // Ambient sound management + void addAmbientSounds(const SLSTRecord &record); + void playAmbientSounds(); + void pauseAmbientSounds(); + void moveAmbientSoundsToPreviousSounds(); + void freePreviousAmbientSounds(); + + // Ambient sound fading + void setTargetVolumes(const SLSTRecord &record); + void applyTargetVolumes(); + void startFadingAmbientSounds(uint16 flags); + void fadeAmbientSoundList(AmbientSoundList &list); + bool fadeVolume(AmbientSound &ambientSound); + bool fadeBalance(AmbientSound &ambientSound); + void setAmbientLooping(bool loop); +}; + +/** + * A sound used internally by the SoundManager + */ +class RivenSound { +public: + RivenSound(MohawkEngine *vm, Audio::RewindableAudioStream *rewindStream); + ~RivenSound(); + + /** Start playing the sound stream passed to the constructor */ + void play(); + + /** Is the sound currently playing ar paused? */ + bool isPlaying() const; + + /** Pause the playback, the play method resumes */ + void pause(); + + /** Get the current volume */ + uint16 getVolume() const; + + /** Change the playback volume */ + void setVolume(uint16 volume); + + /** Get the current balance */ + int16 getBalance() const; + + /** Change the balance */ + void setBalance(int16 balance); + + /** Set the sound to indefinitely loop. Must be called before startting the playback */ + void setLooping(bool loop); + +private: + static byte convertVolume(uint16 volume); + static int8 convertBalance(int16 balance); + + MohawkEngine *_vm; + + Audio::SoundHandle _handle; + Audio::RewindableAudioStream *_stream; + + uint16 _volume; + int16 _balance; + bool _looping; +}; + +} // End of namespace Mohawk + +#endif diff --git a/engines/mohawk/sound.cpp b/engines/mohawk/sound.cpp index a2c08d4a92..2a54696b68 100644 --- a/engines/mohawk/sound.cpp +++ b/engines/mohawk/sound.cpp @@ -23,12 +23,12 @@ #include "common/debug.h" #include "common/events.h" #include "common/system.h" -#include "common/util.h" #include "common/textconsole.h" +#include "audio/mididrv.h" #include "audio/midiparser.h" -#include "audio/musicplugin.h" #include "audio/audiostream.h" +#include "audio/decoders/adpcm.h" #include "audio/decoders/mp3.h" #include "audio/decoders/raw.h" #include "audio/decoders/wave.h" @@ -37,6 +37,150 @@ namespace Mohawk { +Audio::RewindableAudioStream *makeMohawkWaveStream(Common::SeekableReadStream *stream, CueList *cueList) { + uint32 tag = 0; + ADPCMStatus adpcmStatus; + DataChunk dataChunk; + uint32 dataSize = 0; + + memset(&dataChunk, 0, sizeof(DataChunk)); + + if (stream->readUint32BE() != ID_MHWK) // MHWK tag again + error ("Could not find tag 'MHWK'"); + + stream->readUint32BE(); // Skip size + + if (stream->readUint32BE() != ID_WAVE) + error ("Could not find tag 'WAVE'"); + + while (!dataChunk.audioData) { + tag = stream->readUint32BE(); + + switch (tag) { + case ID_ADPC: + debug(2, "Found Tag ADPC"); + // ADPCM Sound Only + // + // This is useful for seeking in the stream, and is actually quite brilliant + // considering some of the other things Broderbund did with the engine. + // Only Riven and CSTime are known to use ADPCM audio and only CSTime + // actually requires this for seeking. On the other hand, it may be interesting + // to look at that one Riven sample that uses the cue points. + // + // Basically, the sample frame from the cue list is looked up here and then + // sets the starting sample and step index at the point specified. Quite + // an elegant/efficient system, really. + + adpcmStatus.size = stream->readUint32BE(); + adpcmStatus.itemCount = stream->readUint16BE(); + adpcmStatus.channels = stream->readUint16BE(); + adpcmStatus.statusItems = new ADPCMStatus::StatusItem[adpcmStatus.itemCount]; + + assert(adpcmStatus.channels <= 2); + + for (uint16 i = 0; i < adpcmStatus.itemCount; i++) { + adpcmStatus.statusItems[i].sampleFrame = stream->readUint32BE(); + + for (uint16 j = 0; j < adpcmStatus.channels; j++) { + adpcmStatus.statusItems[i].channelStatus[j].last = stream->readSint16BE(); + adpcmStatus.statusItems[i].channelStatus[j].stepIndex = stream->readUint16BE(); + } + } + + // TODO: Actually use this chunk. For now, just delete the status items... + delete[] adpcmStatus.statusItems; + break; + case ID_CUE: + debug(2, "Found Tag Cue#"); + // Cues are used for animation sync. There are a couple in Myst and + // Riven but are not used there at all. + + if (!cueList) { + uint32 size = stream->readUint32BE(); + stream->skip(size); + break; + } + + cueList->size = stream->readUint32BE(); + cueList->pointCount = stream->readUint16BE(); + + if (cueList->pointCount == 0) + debug(2, "Cue# chunk found with no points!"); + else + debug(2, "Cue# chunk found with %d point(s)!", cueList->pointCount); + + cueList->points.resize(cueList->pointCount); + for (uint16 i = 0; i < cueList->pointCount; i++) { + cueList->points[i].sampleFrame = stream->readUint32BE(); + + byte nameLength = stream->readByte(); + cueList->points[i].name.clear(); + for (byte j = 0; j < nameLength; j++) + cueList->points[i].name += stream->readByte(); + + // Realign to an even boundary + if (!(nameLength & 1)) + stream->readByte(); + + debug (3, "Cue# chunk point %d (frame %d): %s", i, cueList->points[i].sampleFrame, cueList->points[i].name.c_str()); + } + break; + case ID_DATA: + debug(2, "Found Tag DATA"); + // We subtract 20 from the actual chunk size, which is the total size + // of the chunk's header + dataSize = stream->readUint32BE() - 20; + dataChunk.sampleRate = stream->readUint16BE(); + dataChunk.sampleCount = stream->readUint32BE(); + dataChunk.bitsPerSample = stream->readByte(); + dataChunk.channels = stream->readByte(); + dataChunk.encoding = stream->readUint16BE(); + dataChunk.loopCount = stream->readUint16BE(); + dataChunk.loopStart = stream->readUint32BE(); + dataChunk.loopEnd = stream->readUint32BE(); + + // NOTE: We currently ignore all of the loop parameters here. Myst uses the + // loopCount variable but the loopStart and loopEnd are always 0 and the size of + // the sample. Myst ME doesn't use the Mohawk Sound format and just standard WAVE + // files and therefore does not contain any of this metadata and we have to specify + // whether or not to loop elsewhere. + + dataChunk.audioData = stream->readStream(dataSize); + break; + default: + error ("Unknown tag found in 'tWAV' chunk -- '%s'", tag2str(tag)); + } + } + + // makeMohawkWaveStream always takes control of the original stream + delete stream; + + // The sound in Myst uses raw unsigned 8-bit data + // The sound in the CD version of Riven is encoded in Intel DVI ADPCM + // The sound in the DVD version of Riven is encoded in MPEG-2 Layer II or Intel DVI ADPCM + if (dataChunk.encoding == kCodecRaw) { + byte flags = Audio::FLAG_UNSIGNED; + + if (dataChunk.channels == 2) + flags |= Audio::FLAG_STEREO; + + return Audio::makeRawStream(dataChunk.audioData, dataChunk.sampleRate, flags); + } else if (dataChunk.encoding == kCodecADPCM) { + uint32 blockAlign = dataChunk.channels * dataChunk.bitsPerSample / 8; + return Audio::makeADPCMStream(dataChunk.audioData, DisposeAfterUse::YES, dataSize, Audio::kADPCMDVI, dataChunk.sampleRate, dataChunk.channels, blockAlign); + } else if (dataChunk.encoding == kCodecMPEG2) { +#ifdef USE_MAD + return Audio::makeMP3Stream(dataChunk.audioData, DisposeAfterUse::YES); +#else + warning ("MAD library not included - unable to play MP2 audio"); +#endif + } else { + error ("Unknown Mohawk WAVE encoding %d", dataChunk.encoding); + } + + return nullptr; +} + Sound::Sound(MohawkEngine* vm) : _vm(vm) { _midiDriver = NULL; _midiParser = NULL; @@ -47,7 +191,6 @@ Sound::Sound(MohawkEngine* vm) : _vm(vm) { Sound::~Sound() { stopSound(); - stopAllSLST(); stopBackgroundMyst(); if (_midiParser) { @@ -133,17 +276,6 @@ Audio::SoundHandle *Sound::playSound(uint16 id, byte volume, bool loop, CueList Audio::SoundHandle *Sound::replaceSoundMyst(uint16 id, byte volume, bool loop) { debug (0, "Replacing sound %d", id); - // TODO: The original engine does fading - - Common::String name = _vm->getResourceName(ID_MSND, convertMystID(id)); - - // Check if sound is already playing - for (uint32 i = 0; i < _handles.size(); i++) - if (_handles[i].type == kUsedHandle - && _vm->_mixer->isSoundHandleActive(_handles[i].handle) - && name.equals(_vm->getResourceName(ID_MSND, convertMystID(_handles[i].id)))) - return &_handles[i].handle; - // The original engine also forces looping for those sounds switch (id) { case 2205: @@ -234,300 +366,6 @@ void Sound::stopMidi() { _midiParser->unloadMusic(); } -byte Sound::convertRivenVolume(uint16 volume) { - return (volume == 256) ? 255 : volume; -} - -void Sound::playSLST(uint16 index, uint16 card) { - Common::SeekableReadStream *slstStream = _vm->getResource(ID_SLST, card); - SLSTRecord slstRecord; - uint16 recordCount = slstStream->readUint16BE(); - - for (uint16 i = 0; i < recordCount; i++) { - slstRecord.index = slstStream->readUint16BE(); - slstRecord.sound_count = slstStream->readUint16BE(); - slstRecord.sound_ids = new uint16[slstRecord.sound_count]; - - for (uint16 j = 0; j < slstRecord.sound_count; j++) - slstRecord.sound_ids[j] = slstStream->readUint16BE(); - - slstRecord.fade_flags = slstStream->readUint16BE(); - slstRecord.loop = slstStream->readUint16BE(); - slstRecord.global_volume = slstStream->readUint16BE(); - slstRecord.u0 = slstStream->readUint16BE(); // Unknown - - if (slstRecord.u0 > 1) - warning("slstRecord.u0: %d non-boolean", slstRecord.u0); - - slstRecord.u1 = slstStream->readUint16BE(); // Unknown - - if (slstRecord.u1 != 0) - warning("slstRecord.u1: %d non-zero", slstRecord.u1); - - slstRecord.volumes = new uint16[slstRecord.sound_count]; - slstRecord.balances = new int16[slstRecord.sound_count]; - slstRecord.u2 = new uint16[slstRecord.sound_count]; - - for (uint16 j = 0; j < slstRecord.sound_count; j++) - slstRecord.volumes[j] = slstStream->readUint16BE(); - - for (uint16 j = 0; j < slstRecord.sound_count; j++) - slstRecord.balances[j] = slstStream->readSint16BE(); // negative = left, 0 = center, positive = right - - for (uint16 j = 0; j < slstRecord.sound_count; j++) { - slstRecord.u2[j] = slstStream->readUint16BE(); // Unknown - - if (slstRecord.u2[j] != 255 && slstRecord.u2[j] != 256) - warning("slstRecord.u2[%d]: %d not 255 or 256", j, slstRecord.u2[j]); - } - - if (slstRecord.index == index) { - playSLST(slstRecord); - delete[] slstRecord.sound_ids; - delete[] slstRecord.volumes; - delete[] slstRecord.balances; - delete[] slstRecord.u2; - delete slstStream; - return; - } - - delete[] slstRecord.sound_ids; - delete[] slstRecord.volumes; - delete[] slstRecord.balances; - delete[] slstRecord.u2; - } - - delete slstStream; - - // If we have no matching entries, we do nothing and just let - // the previous ambient sounds continue. -} - -void Sound::playSLST(SLSTRecord slstRecord) { - // End old sounds - for (uint16 i = 0; i < _currentSLSTSounds.size(); i++) { - bool noLongerPlay = true; - for (uint16 j = 0; j < slstRecord.sound_count; j++) - if (_currentSLSTSounds[i].id == slstRecord.sound_ids[j]) - noLongerPlay = false; - if (noLongerPlay) - stopSLSTSound(i, (slstRecord.fade_flags & 1) != 0); - } - - // Start new sounds - for (uint16 i = 0; i < slstRecord.sound_count; i++) { - bool alreadyPlaying = false; - for (uint16 j = 0; j < _currentSLSTSounds.size(); j++) { - if (_currentSLSTSounds[j].id == slstRecord.sound_ids[i]) - alreadyPlaying = true; - } - if (!alreadyPlaying) { - playSLSTSound(slstRecord.sound_ids[i], - (slstRecord.fade_flags & (1 << 1)) != 0, - slstRecord.loop != 0, - slstRecord.volumes[i], - slstRecord.balances[i]); - } - } -} - -void Sound::stopAllSLST(bool fade) { - for (uint16 i = 0; i < _currentSLSTSounds.size(); i++) { - // TODO: Fade out, if requested - _vm->_mixer->stopHandle(*_currentSLSTSounds[i].handle); - delete _currentSLSTSounds[i].handle; - } - - _currentSLSTSounds.clear(); -} - -static int8 convertBalance(int16 balance) { - return (int8)(balance >> 8); -} - -void Sound::playSLSTSound(uint16 id, bool fade, bool loop, uint16 volume, int16 balance) { - // WORKAROUND: Some Riven SLST entries have a volume of 0, so we just ignore them. - if (volume == 0) - return; - - SLSTSndHandle sndHandle; - sndHandle.handle = new Audio::SoundHandle(); - sndHandle.id = id; - _currentSLSTSounds.push_back(sndHandle); - - Audio::RewindableAudioStream *rewindStream = makeMohawkWaveStream(_vm->getResource(ID_TWAV, id)); - - // Loop here if necessary - Audio::AudioStream *audStream = rewindStream; - if (loop) - audStream = Audio::makeLoopingAudioStream(rewindStream, 0); - - // TODO: Handle fading, possibly just raise the volume of the channel in increments? - - _vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, sndHandle.handle, audStream, -1, convertRivenVolume(volume), convertBalance(balance)); -} - -void Sound::stopSLSTSound(uint16 index, bool fade) { - // TODO: Fade out, if requested - _vm->_mixer->stopHandle(*_currentSLSTSounds[index].handle); - delete _currentSLSTSounds[index].handle; - _currentSLSTSounds.remove_at(index); -} - -void Sound::pauseSLST() { - for (uint16 i = 0; i < _currentSLSTSounds.size(); i++) - _vm->_mixer->pauseHandle(*_currentSLSTSounds[i].handle, true); -} - -void Sound::resumeSLST() { - for (uint16 i = 0; i < _currentSLSTSounds.size(); i++) - _vm->_mixer->pauseHandle(*_currentSLSTSounds[i].handle, false); -} - -Audio::RewindableAudioStream *Sound::makeMohawkWaveStream(Common::SeekableReadStream *stream, CueList *cueList) { - uint32 tag = 0; - ADPCMStatus adpcmStatus; - DataChunk dataChunk; - uint32 dataSize = 0; - - memset(&dataChunk, 0, sizeof(DataChunk)); - - if (stream->readUint32BE() != ID_MHWK) // MHWK tag again - error ("Could not find tag 'MHWK'"); - - stream->readUint32BE(); // Skip size - - if (stream->readUint32BE() != ID_WAVE) - error ("Could not find tag 'WAVE'"); - - while (!dataChunk.audioData) { - tag = stream->readUint32BE(); - - switch (tag) { - case ID_ADPC: - debug(2, "Found Tag ADPC"); - // ADPCM Sound Only - // - // This is useful for seeking in the stream, and is actually quite brilliant - // considering some of the other things Broderbund did with the engine. - // Only Riven and CSTime are known to use ADPCM audio and only CSTime - // actually requires this for seeking. On the other hand, it may be interesting - // to look at that one Riven sample that uses the cue points. - // - // Basically, the sample frame from the cue list is looked up here and then - // sets the starting sample and step index at the point specified. Quite - // an elegant/efficient system, really. - - adpcmStatus.size = stream->readUint32BE(); - adpcmStatus.itemCount = stream->readUint16BE(); - adpcmStatus.channels = stream->readUint16BE(); - adpcmStatus.statusItems = new ADPCMStatus::StatusItem[adpcmStatus.itemCount]; - - assert(adpcmStatus.channels <= 2); - - for (uint16 i = 0; i < adpcmStatus.itemCount; i++) { - adpcmStatus.statusItems[i].sampleFrame = stream->readUint32BE(); - - for (uint16 j = 0; j < adpcmStatus.channels; j++) { - adpcmStatus.statusItems[i].channelStatus[j].last = stream->readSint16BE(); - adpcmStatus.statusItems[i].channelStatus[j].stepIndex = stream->readUint16BE(); - } - } - - // TODO: Actually use this chunk. For now, just delete the status items... - delete[] adpcmStatus.statusItems; - break; - case ID_CUE: - debug(2, "Found Tag Cue#"); - // Cues are used for animation sync. There are a couple in Myst and - // Riven but are not used there at all. - - if (!cueList) { - uint32 size = stream->readUint32BE(); - stream->skip(size); - break; - } - - cueList->size = stream->readUint32BE(); - cueList->pointCount = stream->readUint16BE(); - - if (cueList->pointCount == 0) - debug(2, "Cue# chunk found with no points!"); - else - debug(2, "Cue# chunk found with %d point(s)!", cueList->pointCount); - - cueList->points.resize(cueList->pointCount); - for (uint16 i = 0; i < cueList->pointCount; i++) { - cueList->points[i].sampleFrame = stream->readUint32BE(); - - byte nameLength = stream->readByte(); - cueList->points[i].name.clear(); - for (byte j = 0; j < nameLength; j++) - cueList->points[i].name += stream->readByte(); - - // Realign to an even boundary - if (!(nameLength & 1)) - stream->readByte(); - - debug (3, "Cue# chunk point %d (frame %d): %s", i, cueList->points[i].sampleFrame, cueList->points[i].name.c_str()); - } - break; - case ID_DATA: - debug(2, "Found Tag DATA"); - // We subtract 20 from the actual chunk size, which is the total size - // of the chunk's header - dataSize = stream->readUint32BE() - 20; - dataChunk.sampleRate = stream->readUint16BE(); - dataChunk.sampleCount = stream->readUint32BE(); - dataChunk.bitsPerSample = stream->readByte(); - dataChunk.channels = stream->readByte(); - dataChunk.encoding = stream->readUint16BE(); - dataChunk.loopCount = stream->readUint16BE(); - dataChunk.loopStart = stream->readUint32BE(); - dataChunk.loopEnd = stream->readUint32BE(); - - // NOTE: We currently ignore all of the loop parameters here. Myst uses the - // loopCount variable but the loopStart and loopEnd are always 0 and the size of - // the sample. Myst ME doesn't use the Mohawk Sound format and just standard WAVE - // files and therefore does not contain any of this metadata and we have to specify - // whether or not to loop elsewhere. - - dataChunk.audioData = stream->readStream(dataSize); - break; - default: - error ("Unknown tag found in 'tWAV' chunk -- '%s'", tag2str(tag)); - } - } - - // makeMohawkWaveStream always takes control of the original stream - delete stream; - - // The sound in Myst uses raw unsigned 8-bit data - // The sound in the CD version of Riven is encoded in Intel DVI ADPCM - // The sound in the DVD version of Riven is encoded in MPEG-2 Layer II or Intel DVI ADPCM - if (dataChunk.encoding == kCodecRaw) { - byte flags = Audio::FLAG_UNSIGNED; - - if (dataChunk.channels == 2) - flags |= Audio::FLAG_STEREO; - - return Audio::makeRawStream(dataChunk.audioData, dataChunk.sampleRate, flags); - } else if (dataChunk.encoding == kCodecADPCM) { - uint32 blockAlign = dataChunk.channels * dataChunk.bitsPerSample / 8; - return Audio::makeADPCMStream(dataChunk.audioData, DisposeAfterUse::YES, dataSize, Audio::kADPCMDVI, dataChunk.sampleRate, dataChunk.channels, blockAlign); - } else if (dataChunk.encoding == kCodecMPEG2) { -#ifdef USE_MAD - return Audio::makeMP3Stream(dataChunk.audioData, DisposeAfterUse::YES); -#else - warning ("MAD library not included - unable to play MP2 audio"); -#endif - } else { - error ("Unknown Mohawk WAVE encoding %d", dataChunk.encoding); - } - - return NULL; -} - Audio::RewindableAudioStream *Sound::makeLivingBooksWaveStream_v1(Common::SeekableReadStream *stream) { uint16 header = stream->readUint16BE(); uint16 rate = 0; @@ -591,18 +429,6 @@ void Sound::stopSound(uint16 id) { } } -void Sound::pauseSound() { - for (uint32 i = 0; i < _handles.size(); i++) - if (_handles[i].type == kUsedHandle) - _vm->_mixer->pauseHandle(_handles[i].handle, true); -} - -void Sound::resumeSound() { - for (uint32 i = 0; i < _handles.size(); i++) - if (_handles[i].type == kUsedHandle) - _vm->_mixer->pauseHandle(_handles[i].handle, false); -} - bool Sound::isPlaying(uint16 id) { for (uint32 i = 0; i < _handles.size(); i++) if (_handles[i].type == kUsedHandle && _handles[i].id == id) diff --git a/engines/mohawk/sound.h b/engines/mohawk/sound.h index c62e6e9874..2b4b1ce091 100644 --- a/engines/mohawk/sound.h +++ b/engines/mohawk/sound.h @@ -26,9 +26,7 @@ #include "common/scummsys.h" #include "common/str.h" -#include "audio/audiostream.h" #include "audio/mixer.h" -#include "audio/decoders/adpcm.h" #include "mohawk/mohawk.h" #include "mohawk/resource.h" @@ -36,24 +34,14 @@ class MidiDriver; class MidiParser; +namespace Audio { +class RewindableAudioStream; +} + namespace Mohawk { #define MAX_CHANNELS 2 // Can there be more than 2? -struct SLSTRecord { - uint16 index; - uint16 sound_count; - uint16 *sound_ids; - uint16 fade_flags; - uint16 loop; - uint16 global_volume; - uint16 u0; - uint16 u1; - uint16 *volumes; - int16 *balances; - uint16 *u2; -}; - enum SndHandleType { kFreeHandle, kUsedHandle @@ -66,11 +54,6 @@ struct SndHandle { uint16 id; }; -struct SLSTSndHandle { - Audio::SoundHandle *handle; - uint16 id; -}; - struct ADPCMStatus { // Holds ADPCM status data, but is irrelevant for us. uint32 size; uint16 itemCount; @@ -114,6 +97,8 @@ struct DataChunk { Common::SeekableReadStream *audioData; }; +Audio::RewindableAudioStream *makeMohawkWaveStream(Common::SeekableReadStream *stream, CueList *cueList = nullptr); + class MohawkEngine; class Sound { @@ -128,8 +113,6 @@ public: void stopMidi(); void stopSound(); void stopSound(uint16 id); - void pauseSound(); - void resumeSound(); bool isPlaying(uint16 id); bool isPlaying(); uint getNumSamplesPlayed(uint16 id); @@ -142,21 +125,12 @@ public: void stopBackgroundMyst(); void changeBackgroundVolumeMyst(uint16 vol); - // Riven-specific sound functions - void playSLST(uint16 index, uint16 card); - void playSLST(SLSTRecord slstRecord); - void pauseSLST(); - void resumeSLST(); - void stopAllSLST(bool fade = false); - static byte convertRivenVolume(uint16 volume); - private: MohawkEngine *_vm; MidiDriver *_midiDriver; MidiParser *_midiParser; byte *_midiData; - static Audio::RewindableAudioStream *makeMohawkWaveStream(Common::SeekableReadStream *stream, CueList *cueList = NULL); static Audio::RewindableAudioStream *makeLivingBooksWaveStream_v1(Common::SeekableReadStream *stream); void initMidi(); @@ -167,11 +141,6 @@ private: // Myst-specific SndHandle _mystBackgroundSound; - - // Riven-specific - void playSLSTSound(uint16 index, bool fade, bool loop, uint16 volume, int16 balance); - void stopSLSTSound(uint16 id, bool fade); - Common::Array<SLSTSndHandle> _currentSLSTSounds; }; } // End of namespace Mohawk diff --git a/engines/mohawk/video.cpp b/engines/mohawk/video.cpp index 522dd5ecdd..eec543235e 100644 --- a/engines/mohawk/video.cpp +++ b/engines/mohawk/video.cpp @@ -146,7 +146,7 @@ VideoHandle::VideoHandle(const VideoHandle &handle) : _ptr(handle._ptr) { VideoManager::VideoManager(MohawkEngine* vm) : _vm(vm) { // Set dithering enabled, if required - _enableDither = _vm->getGameType() == GType_MYST && !(_vm->getFeatures() & GF_ME); + _enableDither = (_vm->getGameType() == GType_MYST || _vm->getGameType() == GType_MAKINGOF) && !(_vm->getFeatures() & GF_ME); } VideoManager::~VideoManager() { @@ -317,69 +317,8 @@ bool VideoManager::updateMovies() { // Check if we need to draw a frame if (video->needsUpdate()) { - const Graphics::Surface *frame = video->decodeNextFrame(); - Graphics::Surface *convertedFrame = 0; - - if (frame && (*it)->isEnabled()) { - Graphics::PixelFormat pixelFormat = _vm->_system->getScreenFormat(); - - if (frame->format != pixelFormat) { - // We don't support downconverting to 8bpp without having - // support in the codec. Set _enableDither if shows up. - if (pixelFormat.bytesPerPixel == 1) { - warning("Cannot convert high color video frame to 8bpp"); - (*it)->close(); - it = _videos.erase(it); - continue; - } - - // Convert to the current screen format - convertedFrame = frame->convertTo(pixelFormat, video->getPalette()); - frame = convertedFrame; - } else if (pixelFormat.bytesPerPixel == 1 && video->hasDirtyPalette()) { - // Set the palette when running in 8bpp mode only - // Don't do this for Myst, which has its own per-stack handling - if (_vm->getGameType() != GType_MYST) - _vm->_system->getPaletteManager()->setPalette(video->getPalette(), 0, 256); - } - - // Clip the video to make sure it stays on the screen (Myst does this a few times) - Common::Rect targetRect = Common::Rect(video->getWidth(), video->getHeight()); - targetRect.translate((*it)->getX(), (*it)->getY()); - - Common::Rect frameRect = Common::Rect(video->getWidth(), video->getHeight()); - - if (targetRect.left < 0) { - frameRect.left -= targetRect.left; - targetRect.left = 0; - } - - if (targetRect.top < 0) { - frameRect.top -= targetRect.top; - targetRect.top = 0; - } - - if (targetRect.right > _vm->_system->getWidth()) { - frameRect.right -= targetRect.right - _vm->_system->getWidth(); - targetRect.right = _vm->_system->getWidth(); - } - - if (targetRect.bottom > _vm->_system->getHeight()) { - frameRect.bottom -= targetRect.bottom - _vm->_system->getHeight(); - targetRect.bottom = _vm->_system->getHeight(); - } - - _vm->_system->copyRectToScreen(frame->getBasePtr(frameRect.left, frameRect.top), frame->pitch, - targetRect.left, targetRect.top, targetRect.width(), targetRect.height()); - - // We've drawn something to the screen, make sure we update it + if (drawNextFrame(*it)) { updateScreen = true; - - // Delete 8bpp conversion surface - if (convertedFrame) { - convertedFrame->free(); - delete convertedFrame; - } } } @@ -394,6 +333,74 @@ bool VideoManager::updateMovies() { return updateScreen; } +bool VideoManager::drawNextFrame(VideoEntryPtr videoEntry) { + Video::VideoDecoder *video = videoEntry->_video; + const Graphics::Surface *frame = video->decodeNextFrame(); + + if (!frame || !videoEntry->isEnabled()) { + return false; + } + + Graphics::Surface *convertedFrame = 0; + Graphics::PixelFormat pixelFormat = _vm->_system->getScreenFormat(); + + if (frame->format != pixelFormat) { + // We don't support downconverting to 8bpp without having + // support in the codec. Set _enableDither if shows up. + if (pixelFormat.bytesPerPixel == 1) { + warning("Cannot convert high color video frame to 8bpp"); + return false; + } + + // Convert to the current screen format + convertedFrame = frame->convertTo(pixelFormat, video->getPalette()); + frame = convertedFrame; + } else if (pixelFormat.bytesPerPixel == 1 && video->hasDirtyPalette()) { + // Set the palette when running in 8bpp mode only + // Don't do this for Myst, which has its own per-stack handling + if (_vm->getGameType() != GType_MYST) + _vm->_system->getPaletteManager()->setPalette(video->getPalette(), 0, 256); + } + + // Clip the video to make sure it stays on the screen (Myst does this a few times) + Common::Rect targetRect = Common::Rect(video->getWidth(), video->getHeight()); + targetRect.translate(videoEntry->getX(), videoEntry->getY()); + + Common::Rect frameRect = Common::Rect(video->getWidth(), video->getHeight()); + + if (targetRect.left < 0) { + frameRect.left -= targetRect.left; + targetRect.left = 0; + } + + if (targetRect.top < 0) { + frameRect.top -= targetRect.top; + targetRect.top = 0; + } + + if (targetRect.right > _vm->_system->getWidth()) { + frameRect.right -= targetRect.right - _vm->_system->getWidth(); + targetRect.right = _vm->_system->getWidth(); + } + + if (targetRect.bottom > _vm->_system->getHeight()) { + frameRect.bottom -= targetRect.bottom - _vm->_system->getHeight(); + targetRect.bottom = _vm->_system->getHeight(); + } + + _vm->_system->copyRectToScreen(frame->getBasePtr(frameRect.left, frameRect.top), frame->pitch, + targetRect.left, targetRect.top, targetRect.width(), targetRect.height()); + + // Delete 8bpp conversion surface + if (convertedFrame) { + convertedFrame->free(); + delete convertedFrame; + } + + // We've drawn something to the screen, make sure we update it + return true; +} + void VideoManager::activateMLST(uint16 mlstId, uint16 card) { Common::SeekableReadStream *mlstStream = _vm->getResource(ID_MLST, card); uint16 recordCount = mlstStream->readUint16BE(); @@ -582,11 +589,9 @@ bool VideoManager::isVideoPlaying() { } void VideoManager::drawVideoFrame(VideoHandle handle, const Audio::Timestamp &time) { - // FIXME: This should be done separately from the "playing" - // videos eventually. assert(handle); handle->seek(time); - updateMovies(); + drawNextFrame(handle._ptr); handle->stop(); } diff --git a/engines/mohawk/video.h b/engines/mohawk/video.h index c5a3dc7041..e2b09b6afd 100644 --- a/engines/mohawk/video.h +++ b/engines/mohawk/video.h @@ -346,10 +346,12 @@ private: // Utility functions for managing entries VideoEntryPtr open(uint16 id); VideoEntryPtr open(const Common::String &fileName); - + VideoList::iterator findEntry(VideoEntryPtr ptr); void removeEntry(VideoEntryPtr ptr); + bool drawNextFrame(VideoEntryPtr videoEntry); + // Dithering control bool _enableDither; void checkEnableDither(VideoEntryPtr &entry); diff --git a/engines/mohawk/view.cpp b/engines/mohawk/view.cpp index 1aaf32ea78..70d20270a5 100644 --- a/engines/mohawk/view.cpp +++ b/engines/mohawk/view.cpp @@ -37,6 +37,23 @@ Module::~Module() { } Feature::Feature(View *view) : _view(view) { + _next = _prev = nullptr; + _drawProc = nullptr; + _moveProc = nullptr; + _doneProc = nullptr; + _frameProc = nullptr; + _timeProc = nullptr; + _region = 0; + _id = 0; + _scrbId = 0; + _storedScrbId = 0; + _flags = 0; + _nextTime = 0; + _delayTime = 0; + _dirty = false; + _needsReset = false; + _justReset = false; + _done = false; } Feature::~Feature() { @@ -75,11 +92,10 @@ void Feature::setNodeDefaults(Feature *prev, Feature *next) { _flags = 0; - _dirty = 1; - _needsReset = 1; - _justReset = 0; // old - _notifyDone = 0; - _done = 0; // new + _dirty = true; + _needsReset = true; + _justReset = false; // old + _done = false; // new _nextTime = 0; _delayTime = 0; @@ -107,11 +123,11 @@ void Feature::resetFeatureScript(uint16 enabled, uint16 scrbId) { resetFrame(); _nextTime = 0; // New feature code uses _view->_lastIdleTime, but should be equivalent. _data.enabled = enabled; - _dirty = 1; + _dirty = true; finishResetFeatureScript(); - _needsReset = 0; + _needsReset = false; if (_region) { // TODO: mark _region as dirty @@ -123,7 +139,6 @@ void Feature::resetFeatureScript(uint16 enabled, uint16 scrbId) { void Feature::resetFeature(bool notifyDone, Module::FeatureProc doneProc, uint16 scrbId) { resetFeatureScript(1, scrbId); _doneProc = doneProc; - _notifyDone = notifyDone; } void Feature::hide(bool clip) { @@ -159,7 +174,7 @@ void Feature::moveAndUpdate(Common::Point newPos) { return; _nextTime = 0; - _dirty = 1; + _dirty = true; // TODO: mark _data.bounds as dirty if (_data.bitmapIds[0]) @@ -228,7 +243,7 @@ void OldFeature::resetScript() { } void OldFeature::finishResetFeatureScript() { - _justReset = 1; + _justReset = true; if (_flags & kFeatureOldAdjustByPos) { Common::SeekableReadStream *ourSCRB = _view->getSCRB(_data.scrbIndex, _scrbId); @@ -240,6 +255,13 @@ void OldFeature::finishResetFeatureScript() { } NewFeature::NewFeature(View *view) : Feature(view) { + _unknown168 = 0; + _pickupProc = nullptr; + _dropProc = nullptr; + _dragMoveProc = nullptr; + _oldMoveProc = nullptr; + _dragFlags = 0; + _oldFlags = 0; } NewFeature::~NewFeature() { @@ -307,7 +329,7 @@ void NewFeature::resetScript() { } void NewFeature::finishResetFeatureScript() { - _done = 0; + _done = false; } View::View(MohawkEngine *vm) : _vm(vm) { @@ -319,6 +341,12 @@ View::View(MohawkEngine *vm) : _vm(vm) { _compoundSHAPGroups[i] = 0; } _numSCRBGroups = 0; + + _lastIdleTime = 0; + _needsUpdate = false; + _gfx = nullptr; + _rootNode = nullptr; + _cursorNode = nullptr; } View::~View() { @@ -347,7 +375,7 @@ void View::idleView() { } if (node->_drawProc) (_currentModule->*(node->_drawProc))(node); - node->_dirty = 0; + node->_dirty = false; } if (_needsUpdate) { diff --git a/engines/mohawk/view.h b/engines/mohawk/view.h index 47853f056f..463715b765 100644 --- a/engines/mohawk/view.h +++ b/engines/mohawk/view.h @@ -138,11 +138,10 @@ public: uint32 _flags; uint32 _nextTime; uint32 _delayTime; - uint16 _dirty; // byte in old - byte _needsReset; - byte _justReset; // old - byte _notifyDone; // old - byte _done; // new + bool _dirty; // byte in old + bool _needsReset; + bool _justReset; // old + bool _done; // new FeatureData _data; @@ -192,13 +191,6 @@ protected: void finishResetFeatureScript(); }; -#define NUM_SYNC_CHANNELS 17 -struct SyncChannel { - uint16 masterId; - byte state; - bool alternate; -}; - class View { public: View(MohawkEngine *vm); @@ -234,7 +226,6 @@ public: void sortView(); uint32 _lastIdleTime; - SyncChannel _syncChannels[NUM_SYNC_CHANNELS]; virtual uint32 getTime() = 0; |