/* 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. * */ /* * This code is based on original Mortville Manor DOS source code * Copyright (c) 1987-1989 Lankhor */ #include "mortevielle/mortevielle.h" #include "mortevielle/dialogs.h" #include "mortevielle/menu.h" #include "mortevielle/mouse.h" #include "mortevielle/outtext.h" #include "mortevielle/saveload.h" #include "mortevielle/outtext.h" #include "common/system.h" #include "common/config-manager.h" #include "common/debug-channels.h" #include "engines/util.h" #include "engines/engine.h" #include "graphics/palette.h" #include "graphics/pixelformat.h" namespace Mortevielle { MortevielleEngine *g_vm; MortevielleEngine::MortevielleEngine(OSystem *system, const MortevielleGameDescription *gameDesc): Engine(system), _gameDescription(gameDesc), _randomSource("mortevielle"), _soundManager(_mixer) { g_vm = this; _debugger.setParent(this); _dialogManager.setParent(this); _screenSurface.setParent(this); _mouse.setParent(this); _text.setParent(this); _soundManager.setParent(this); _savegameManager.setParent(this); _menu.setParent(this); _lastGameFrame = 0; _mouseClick = false; _inMainGameLoop = false; _quitGame = false; _roomPresenceLuc = false; _roomPresenceIda = false; _purpleRoomPresenceLeo = false; _roomPresenceGuy = false; _roomPresenceEva = false; _roomPresenceMax = false; _roomPresenceBob = false; _roomPresencePat = false; _toiletsPresenceBobMax = false; _bathRoomPresenceBobMax = false; _juliaRoomPresenceLeo = false; _soundOff = false; _largestClearScreen = false; _hiddenHero = false; _heroSearching = false; _keyPressedEsc = false; _reloadCFIEC = false; _blo = false; _col = false; _syn = false; _obpart = false; _destinationOk = false; _anyone = false; _uptodatePresence = false; _textColor = 0; _place = -1; _x26KeyCount = -1; _caff = -1; _day = 0; _curPict = nullptr; _curAnim = nullptr; _rightFramePict = nullptr; } MortevielleEngine::~MortevielleEngine() { free(_curPict); free(_curAnim); free(_rightFramePict); } /** * Specifies whether the engine supports given features */ bool MortevielleEngine::hasFeature(EngineFeature f) const { return (f == kSupportsRTL) || (f == kSupportsLoadingDuringRuntime) || (f == kSupportsSavingDuringRuntime); } /** * Return true if a game can currently be loaded */ bool MortevielleEngine::canLoadGameStateCurrently() { // Saving is only allowed in the main game event loop return _inMainGameLoop; } /** * Return true if a game can currently be saved */ bool MortevielleEngine::canSaveGameStateCurrently() { // Loading is only allowed in the main game event loop return _inMainGameLoop; } /** * Load in a savegame at the specified slot number */ Common::Error MortevielleEngine::loadGameState(int slot) { return _savegameManager.loadGame(slot); } /** * Save the current game */ Common::Error MortevielleEngine::saveGameState(int slot, const Common::String &desc) { if (slot == 0) return Common::kWritingFailed; return _savegameManager.saveGame(slot, desc); } /** * Support method that generates a savegame name * @param slot Slot number */ Common::String MortevielleEngine::generateSaveFilename(const Common::String &target, int slot) { if (slot == 0) // Initial game state loaded when the game starts return "sav0.mor"; return Common::String::format("%s.%03d", target.c_str(), slot); } /** * Initialize the game state */ Common::ErrorCode MortevielleEngine::initialize() { // Initialize graphics mode initGraphics(SCREEN_WIDTH, SCREEN_HEIGHT, true); // Set debug channels DebugMan.addDebugChannel(kMortevielleCore, "core", "Core debugging"); DebugMan.addDebugChannel(kMortevielleGraphics, "graphics", "Graphics debugging"); // Set up an intermediate screen surface _screenSurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, Graphics::PixelFormat::createFormatCLUT8()); _txxFileFl = false; // Load texts from TXX files loadTexts(); // Load the mort.dat resource Common::ErrorCode result = loadMortDat(); if (result != Common::kNoError) { _screenSurface.free(); return result; } // Load some error messages (was previously in chartex()) _hintPctMessage = getString(580); // You should have noticed %d hints // Set default EGA palette _paletteManager.setDefaultPalette(); // Setup the mouse cursor initMouse(); loadPalette(); loadCFIPH(); loadCFIEC(); decodeNumber(&_cfiecBuffer[161 * 16], (_cfiecBufferSize - (161 * 16)) / 64); _x26KeyCount = 1; initMaxAnswer(); initMouse(); loadPlaces(); _soundOff = false; _largestClearScreen = false; testKeyboard(); showConfigScreen(); testKeyboard(); clearScreen(); _soundManager.loadNoise(); _soundManager.loadAmbiantSounds(); return Common::kNoError; } /** * Loads the contents of the mort.dat data file */ Common::ErrorCode MortevielleEngine::loadMortDat() { Common::File f; // Open the mort.dat file if (!f.open(MORT_DAT)) { GUIErrorMessage("Could not locate 'mort.dat'."); return Common::kReadingFailed; } // Validate the data file header char fileId[4]; f.read(fileId, 4); if (strncmp(fileId, "MORT", 4) != 0) { GUIErrorMessage("The located mort.dat data file is invalid"); return Common::kReadingFailed; } // Check the version if (f.readByte() < MORT_DAT_REQUIRED_VERSION) { GUIErrorMessage("The located mort.dat data file is too old, please download an updated version on scummvm.org"); return Common::kReadingFailed; } f.readByte(); // Minor version // Loop to load resources from the data file while (f.pos() < f.size()) { // Get the Id and size of the next resource char dataType[4]; int dataSize; f.read(dataType, 4); dataSize = f.readUint16LE(); if (!strncmp(dataType, "FONT", 4)) { // Font resource _screenSurface.readFontData(f, dataSize); } else if (!strncmp(dataType, "SSTR", 4)) { readStaticStrings(f, dataSize, kStaticStrings); } else if ((!strncmp(dataType, "GSTR", 4)) && (!_txxFileFl)) { readStaticStrings(f, dataSize, kGameStrings); } else if (!strncmp(dataType, "VERB", 4)) { _menu.readVerbNums(f, dataSize); } else { // Unknown section f.skip(dataSize); } } // Close the file f.close(); assert(_engineStrings.size() > 0); return Common::kNoError; } /** * Read in a static strings block, and if the language matches, load up the static strings */ void MortevielleEngine::readStaticStrings(Common::File &f, int dataSize, DataType dataType) { // Figure out what language Id is needed byte desiredLanguageId; switch(getLanguage()) { case Common::EN_ANY: desiredLanguageId = MORTDAT_LANG_ENGLISH; break; case Common::FR_FRA: desiredLanguageId = MORTDAT_LANG_FRENCH; break; case Common::DE_DEU: desiredLanguageId = MORTDAT_LANG_GERMAN; break; default: warning("Language not supported, switching to English"); desiredLanguageId = MORTDAT_LANG_ENGLISH; break; } // Read in the language byte languageId = f.readByte(); --dataSize; // If the language isn't correct, then skip the entire block if (languageId != desiredLanguageId) { f.skip(dataSize); return; } // Load in each of the strings while (dataSize > 0) { Common::String s; char ch; while ((ch = (char)f.readByte()) != '\0') s += ch; if (dataType == kStaticStrings) _engineStrings.push_back(s); else if (dataType == kGameStrings) _gameStrings.push_back(s); dataSize -= s.size() + 1; } assert(dataSize == 0); } /*-------------------------------------------------------------------------*/ Common::Error MortevielleEngine::run() { // Initialize the game Common::ErrorCode err = initialize(); if (err != Common::kNoError) return err; // Check for a savegame int loadSlot = 0; if (ConfMan.hasKey("save_slot")) { int gameToLoad = ConfMan.getInt("save_slot"); if ((gameToLoad >= 1) && (gameToLoad <= 999)) loadSlot = gameToLoad; } if (loadSlot == 0) // Show the game introduction showIntroduction(); else { _caff = 51; _text.taffich(); } // Either load the initial game state savegame, or the specified savegame number adzon(); resetVariables(); if (loadSlot != 0) _savegameManager.loadSavegame(generateSaveFilename(loadSlot)); // Run the main game loop mainGame(); // Cleanup (allocated in initialize()) _screenSurface.free(); free(_soundManager._cfiphBuffer); free(_cfiecBuffer); return Common::kNoError; } /** * Show the game introduction */ void MortevielleEngine::showIntroduction() { _dialogManager.displayIntroScreen(false); _dialogManager.checkForF8(142, false); if (shouldQuit()) return; _dialogManager.displayIntroFrame2(); _dialogManager.checkForF8(143, true); if (shouldQuit()) return; showTitleScreen(); music(); _mixer->stopAll(); } /** * Main game loop. Handles potentially playing the game multiple times, such as if the player * loses, and chooses to start playing the game again. */ void MortevielleEngine::mainGame() { if (_reloadCFIEC) loadCFIEC(); for (_crep = 1; _crep <= _x26KeyCount; ++_crep) decodeNumber(&_cfiecBuffer[161 * 16], (_cfiecBufferSize - (161 * 16)) / 64); _menu.initMenu(); charToHour(); initGame(); clearScreen(); drawRightFrame(); _mouse.showMouse(); // Loop to play the game do { playGame(); if (shouldQuit()) return; } while (!_quitGame); } /** * This method handles playing a loaded game * @remarks Originally called tjouer */ void MortevielleEngine::playGame() { gameLoaded(); // Loop handling actions until the game has to be quit, or show the lose or end sequence do { handleAction(); if (shouldQuit()) return; } while (!((_quitGame) || (_endGame) || (_loseGame))); if (_endGame) endGame(); else if (_loseGame) askRestart(); } } // End of namespace Mortevielle