ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

stark.cpp

Go to the documentation of this file.
00001 /* ResidualVM - A 3D game interpreter
00002  *
00003  * ResidualVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the AUTHORS
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 #include "engines/stark/stark.h"
00024 
00025 #include "engines/stark/console.h"
00026 #include "engines/stark/debug.h"
00027 #include "engines/stark/resources/level.h"
00028 #include "engines/stark/resources/location.h"
00029 #include "engines/stark/savemetadata.h"
00030 #include "engines/stark/scene.h"
00031 #include "engines/stark/services/userinterface.h"
00032 #include "engines/stark/services/archiveloader.h"
00033 #include "engines/stark/services/dialogplayer.h"
00034 #include "engines/stark/services/diary.h"
00035 #include "engines/stark/services/fontprovider.h"
00036 #include "engines/stark/services/gameinterface.h"
00037 #include "engines/stark/services/global.h"
00038 #include "engines/stark/services/resourceprovider.h"
00039 #include "engines/stark/services/services.h"
00040 #include "engines/stark/services/stateprovider.h"
00041 #include "engines/stark/services/staticprovider.h"
00042 #include "engines/stark/services/settings.h"
00043 #include "engines/stark/services/gamechapter.h"
00044 #include "engines/stark/services/gamemessage.h"
00045 #include "engines/stark/gfx/driver.h"
00046 #include "engines/stark/gfx/framelimiter.h"
00047 
00048 #include "audio/mixer.h"
00049 #include "common/config-manager.h"
00050 #include "common/debug-channels.h"
00051 #include "common/events.h"
00052 #include "common/fs.h"
00053 #include "common/random.h"
00054 #include "common/savefile.h"
00055 #include "common/system.h"
00056 #include "common/translation.h"
00057 #include "gui/message.h"
00058 
00059 namespace Stark {
00060 
00061 StarkEngine::StarkEngine(OSystem *syst, const ADGameDescription *gameDesc) :
00062         Engine(syst),
00063         _frameLimiter(nullptr),
00064         _console(nullptr),
00065         _gameDescription(gameDesc),
00066         _lastClickTime(0),
00067         _lastAutoSaveTime(0) {
00068     // Add the available debug channels
00069     DebugMan.addDebugChannel(kDebugArchive, "Archive", "Debug the archive loading");
00070     DebugMan.addDebugChannel(kDebugXMG, "XMG", "Debug the loading of XMG images");
00071     DebugMan.addDebugChannel(kDebugXRC, "XRC", "Debug the loading of XRC resource trees");
00072     DebugMan.addDebugChannel(kDebugModding, "Modding", "Debug the loading of modded assets");
00073     DebugMan.addDebugChannel(kDebugAnimation, "Animation", "Debug the animation changes");
00074     DebugMan.addDebugChannel(kDebugUnknown, "Unknown", "Debug unknown values on the data");
00075 
00076     addModsToSearchPath();
00077 }
00078 
00079 StarkEngine::~StarkEngine() {
00080     delete StarkServices::instance().gameInterface;
00081     delete StarkServices::instance().diary;
00082     delete StarkServices::instance().dialogPlayer;
00083     delete StarkServices::instance().randomSource;
00084     delete StarkServices::instance().scene;
00085     delete StarkServices::instance().gfx;
00086     delete StarkServices::instance().staticProvider;
00087     delete StarkServices::instance().resourceProvider;
00088     delete StarkServices::instance().global;
00089     delete StarkServices::instance().stateProvider;
00090     delete StarkServices::instance().archiveLoader;
00091     delete StarkServices::instance().userInterface;
00092     delete StarkServices::instance().fontProvider;
00093     delete StarkServices::instance().settings;
00094     delete StarkServices::instance().gameChapter;
00095     delete StarkServices::instance().gameMessage;
00096 
00097     StarkServices::destroy();
00098 
00099     delete _console;
00100     delete _frameLimiter;
00101 }
00102 
00103 Common::Error StarkEngine::run() {
00104     _console = new Console();
00105     _frameLimiter = new Gfx::FrameLimiter(_system, ConfMan.getInt("engine_speed"));
00106     _lastAutoSaveTime = _system->getMillis();
00107 
00108     // Get the screen prepared
00109     Gfx::Driver *gfx = Gfx::Driver::create();
00110     gfx->init();
00111 
00112     checkRecommendedDatafiles();
00113 
00114     // Setup the public services
00115     StarkServices &services = StarkServices::instance();
00116     services.gfx = gfx;
00117     services.archiveLoader = new ArchiveLoader();
00118     services.stateProvider = new StateProvider();
00119     services.global = new Global();
00120     services.resourceProvider = new ResourceProvider(services.archiveLoader, services.stateProvider, services.global);
00121     services.staticProvider = new StaticProvider(services.archiveLoader);
00122     services.randomSource = new Common::RandomSource("stark");
00123     services.fontProvider = new FontProvider();
00124     services.scene = new Scene(services.gfx);
00125     services.dialogPlayer = new DialogPlayer();
00126     services.diary = new Diary();
00127     services.gameInterface = new GameInterface();
00128     services.userInterface = new UserInterface(services.gfx);
00129     services.settings = new Settings(_mixer, _gameDescription);
00130     services.gameChapter = new GameChapter();
00131     services.gameMessage = new GameMessage();
00132 
00133     // Load global resources
00134     services.staticProvider->init();
00135     services.fontProvider->initFonts();
00136 
00137     // Apply the sound volume settings
00138     syncSoundSettings();
00139 
00140     // Initialize the UI
00141     services.userInterface->init();
00142 
00143     // Load through ResidualVM launcher
00144     if (ConfMan.hasKey("save_slot")) {
00145         loadGameState(ConfMan.getInt("save_slot"));
00146     }
00147 
00148     // Start running
00149     mainLoop();
00150 
00151     services.staticProvider->shutdown();
00152     services.resourceProvider->shutdown();
00153 
00154     return Common::kNoError;
00155 }
00156 
00157 void StarkEngine::mainLoop() {
00158     while (!shouldQuit()) {
00159         _frameLimiter->startFrame();
00160 
00161         processEvents();
00162 
00163         if (StarkUserInterface->shouldExit()) {
00164             quitGame();
00165             break;
00166         }
00167 
00168         if (shouldPerformAutoSave(_lastAutoSaveTime)) {
00169             tryAutoSaving();
00170         }
00171 
00172         if (StarkResourceProvider->hasLocationChangeRequest()) {
00173             StarkGlobal->setNormalSpeed();
00174             StarkResourceProvider->performLocationChange();
00175         }
00176 
00177         StarkUserInterface->doQueuedScreenChange();
00178 
00179         updateDisplayScene();
00180 
00181         // Swap buffers
00182         _frameLimiter->delayBeforeSwap();
00183         StarkGfx->flipBuffer();
00184     }
00185 }
00186 
00187 void StarkEngine::processEvents() {
00188     Common::Event e;
00189     while (g_system->getEventManager()->pollEvent(e)) {
00190         // Handle any buttons, keys and joystick operations
00191 
00192         if (isPaused()) {
00193             // Only pressing key P to resume the game is allowed when the game is paused
00194             if (e.type == Common::EVENT_KEYDOWN && e.kbd.keycode == Common::KEYCODE_p) {
00195                 pauseEngine(false);
00196             }
00197             continue;
00198         }
00199 
00200         if (e.type == Common::EVENT_KEYDOWN) {
00201             if (e.kbdRepeat) {
00202                 continue;
00203             }
00204 
00205             if (e.kbd.keycode == Common::KEYCODE_d && (e.kbd.hasFlags(Common::KBD_CTRL))) {
00206                 _console->attach();
00207                 _console->onFrame();
00208             } else if ((e.kbd.keycode == Common::KEYCODE_RETURN || e.kbd.keycode == Common::KEYCODE_KP_ENTER)
00209                         && e.kbd.hasFlags(Common::KBD_ALT)) {
00210                     StarkGfx->toggleFullscreen();
00211             } else if (e.kbd.keycode == Common::KEYCODE_p) {
00212                 if (StarkUserInterface->isInGameScreen()) {
00213                     pauseEngine(true);
00214                     debug("The game is paused");
00215                 }
00216             } else {
00217                 StarkUserInterface->handleKeyPress(e.kbd);
00218             }
00219 
00220         } else if (e.type == Common::EVENT_LBUTTONUP) {
00221             StarkUserInterface->handleMouseUp();
00222         } else if (e.type == Common::EVENT_MOUSEMOVE) {
00223             StarkUserInterface->handleMouseMove(e.mouse);
00224         } else if (e.type == Common::EVENT_LBUTTONDOWN) {
00225             StarkUserInterface->handleClick();
00226             if (_system->getMillis() - _lastClickTime < _doubleClickDelay) {
00227                 StarkUserInterface->handleDoubleClick();
00228             }
00229             _lastClickTime = _system->getMillis();
00230         } else if (e.type == Common::EVENT_RBUTTONDOWN) {
00231             StarkUserInterface->handleRightClick();
00232         } else if (e.type == Common::EVENT_SCREEN_CHANGED) {
00233             onScreenChanged();
00234         }
00235     }
00236 }
00237 
00238 void StarkEngine::updateDisplayScene() {
00239     if (StarkGlobal->isFastForward()) {
00240         // The original engine was frame limited to 30 fps.
00241         // Set the frame duration to 1000 / 30 ms so that fast forward
00242         // skips the same amount of simulated time as the original.
00243         StarkGlobal->setMillisecondsPerGameloop(33);
00244     } else {
00245         StarkGlobal->setMillisecondsPerGameloop(_frameLimiter->getLastFrameDuration());
00246     }
00247 
00248     // Clear the screen
00249     StarkGfx->clearScreen();
00250 
00251     // Only update the world resources when on the game screen
00252     if (StarkUserInterface->isInGameScreen() && !isPaused()) {
00253         int frames = 0;
00254         do {
00255             // Update the game resources
00256             StarkGlobal->getLevel()->onGameLoop();
00257             StarkGlobal->getCurrent()->getLevel()->onGameLoop();
00258             StarkGlobal->getCurrent()->getLocation()->onGameLoop();
00259             frames++;
00260 
00261             // When the game is in fast forward mode, update
00262             // the game resources for multiple frames,
00263             // but render only once.
00264         } while (StarkGlobal->isFastForward() && frames < 100);
00265         StarkGlobal->setNormalSpeed();
00266     }
00267 
00268     // Render the current scene
00269     // Update the UI state before displaying the scene
00270     StarkUserInterface->onGameLoop();
00271 
00272     // Tell the UI to render, and update implicitly, if this leads to new mouse-over events.
00273     StarkUserInterface->render();
00274 }
00275 
00276 static bool modsCompare(const Common::FSNode &a, const Common::FSNode &b) {
00277     return a.getName() < b.getName();
00278 }
00279 
00280 void StarkEngine::addModsToSearchPath() const {
00281     const Common::FSNode gameDataDir(ConfMan.get("path"));
00282     const Common::FSNode modsDir = gameDataDir.getChild("mods");
00283     if (modsDir.exists()) {
00284         Common::FSList list;
00285         modsDir.getChildren(list);
00286 
00287         Common::sort(list.begin(), list.end(), modsCompare);
00288 
00289         for (uint i = 0; i < list.size(); i++) {
00290             SearchMan.addDirectory("mod_" + list[i].getName(), list[i], 0, 4);
00291         }
00292     }
00293 }
00294 
00295 void StarkEngine::checkRecommendedDatafiles() {
00296     ConfMan.registerDefault("warn_about_missing_files", true);
00297 
00298     if (!ConfMan.getBool("warn_about_missing_files")) {
00299         return;
00300     }
00301 
00302     Common::String message = _("You are missing recommended data files:");
00303 
00304     const Common::FSNode gameDataDir(ConfMan.get("path"));
00305     Common::FSNode fontsDir = gameDataDir.getChild("fonts");
00306     if (!fontsDir.isDirectory()) {
00307         fontsDir = gameDataDir.getChild("Fonts"); // FSNode is case sensitive
00308     }
00309     if (!fontsDir.isDirectory()) {
00310         fontsDir = gameDataDir.getChild("FONTS");
00311     }
00312 
00313     bool missingFiles = false;
00314     if (!fontsDir.isDirectory()) {
00315         message += "\n\n";
00316         message += _("The 'fonts' folder is required to experience the text style as it was designed. "
00317                 "The Steam release is known to be missing it. You can get the fonts from the demo version of the game.");
00318         missingFiles = true;
00319     }
00320 
00321     if (!SearchMan.hasFile("gui.ini")) {
00322         message += "\n\n";
00323         message += _("'gui.ini' is recommended to get proper font settings for the game localization.");
00324         missingFiles = true;
00325     }
00326 
00327     if (!SearchMan.hasFile("language.ini")) {
00328         message += "\n\n";
00329         message += _("'language.ini' is recommended to get localized confirmation dialogs.");
00330         missingFiles = true;
00331     }
00332 
00333     if (!SearchMan.hasFile("game.exe") && !SearchMan.hasFile("game.dll")) {
00334         message += "\n\n";
00335         message += _("'game.exe' is recommended to get styled confirmation dialogs.");
00336         missingFiles = true;
00337     }
00338 
00339     if (missingFiles) {
00340         warning("%s", message.c_str());
00341 
00342         GUI::MessageDialog dialog(message);
00343         dialog.runModal();
00344     }
00345 }
00346 
00347 bool StarkEngine::hasFeature(EngineFeature f) const {
00348     return
00349         (f == kSupportsLoadingDuringRuntime) ||
00350         (f == kSupportsSavingDuringRuntime) ||
00351         (f == kSupportsArbitraryResolutions) ||
00352         (f == kSupportsRTL);
00353 }
00354 
00355 bool StarkEngine::canLoadGameStateCurrently() {
00356     return !StarkUserInterface->isInSaveLoadMenuScreen();
00357 }
00358 
00359 Common::Error StarkEngine::loadGameState(int slot) {
00360     // Open the save file
00361     Common::String filename = formatSaveName(_targetName.c_str(), slot);
00362     Common::InSaveFile *save = _saveFileMan->openForLoading(filename);
00363     if (!save) {
00364         return _saveFileMan->getError();
00365     }
00366 
00367     StateReadStream stream(save);
00368 
00369     // Read the header
00370     SaveMetadata metadata;
00371     Common::ErrorCode metadataErrorCode = metadata.read(&stream, filename);
00372     if (metadataErrorCode != Common::kNoError) {
00373         return metadataErrorCode;
00374     }
00375 
00376     // Reset the UI
00377     StarkUserInterface->skipFMV();
00378     StarkUserInterface->clearLocationDependentState();
00379     StarkUserInterface->setInteractive(true);
00380     StarkUserInterface->changeScreen(Screen::kScreenGame);
00381     StarkUserInterface->inventoryOpen(false);
00382     StarkUserInterface->restoreScreenHistory();
00383 
00384     // Clear the previous world resources
00385     StarkResourceProvider->shutdown();
00386 
00387     if (metadata.version >= 9) {
00388         metadata.skipGameScreenThumbnail(&stream);
00389     }
00390 
00391     // Read the resource trees state
00392     StarkStateProvider->readStateFromStream(&stream, metadata.version);
00393 
00394     // Read the diary state
00395     StarkDiary->readStateFromStream(&stream, metadata.version);
00396 
00397     // Read the location stack
00398     StarkResourceProvider->readLocationStack(&stream, metadata.version);
00399 
00400     if (stream.eos()) {
00401         warning("Unexpected end of file reached when reading '%s'", filename.c_str());
00402         return Common::kReadingFailed;
00403     }
00404 
00405     // Initialize the world resources with the loaded state
00406     StarkResourceProvider->initGlobal();
00407     StarkResourceProvider->setShouldRestoreCurrentState();
00408     StarkResourceProvider->requestLocationChange(metadata.levelIndex, metadata.locationIndex);
00409 
00410     if (metadata.version >= 9) {
00411         setTotalPlayTime(metadata.totalPlayTime);
00412     }
00413 
00414     return Common::kNoError;
00415 }
00416 
00417 bool StarkEngine::canSaveGameStateCurrently() {
00418     // Disallow saving when there is no level loaded or when a script is running
00419     // or when the save & load menu is currently displayed
00420     return StarkGlobal->getLevel() && StarkGlobal->getCurrent() && StarkUserInterface->isInteractive() && !StarkUserInterface->isInSaveLoadMenuScreen();
00421 }
00422 
00423 Common::Error StarkEngine::saveGameState(int slot, const Common::String &desc) {
00424     // Ensure the state store is up to date
00425     StarkResourceProvider->commitActiveLocationsState();
00426 
00427     // Open the save file
00428     Common::String filename = formatSaveName(_targetName.c_str(), slot);
00429     Common::OutSaveFile *save = _saveFileMan->openForSaving(filename);
00430     if (!save) {
00431         return _saveFileMan->getError();
00432     }
00433 
00434     // 1. Write the header
00435     SaveMetadata metadata;
00436     metadata.description = desc;
00437     metadata.version = StateProvider::kSaveVersion;
00438     metadata.levelIndex = StarkGlobal->getCurrent()->getLevel()->getIndex();
00439     metadata.locationIndex = StarkGlobal->getCurrent()->getLocation()->getIndex();
00440     metadata.totalPlayTime = getTotalPlayTime();
00441     metadata.gameWindowThumbnail = StarkUserInterface->getGameWindowThumbnail();
00442 
00443     TimeDate timeDate;
00444     _system->getTimeAndDate(timeDate);
00445     metadata.setSaveTime(timeDate);
00446 
00447     metadata.write(save);
00448     metadata.writeGameScreenThumbnail(save);
00449 
00450     // 2. Write the resource trees state
00451     StarkStateProvider->writeStateToStream(save);
00452 
00453     // 3. Write the diary state
00454     StarkDiary->writeStateToStream(save);
00455 
00456     // 4. Write the location stack
00457     StarkResourceProvider->writeLocationStack(save);
00458 
00459     delete save;
00460 
00461     return Common::kNoError;
00462 }
00463 
00464 Common::String StarkEngine::formatSaveName(const char *target, int slot) {
00465     return Common::String::format("%s-%03d.tlj", target, slot);
00466 }
00467 
00468 void StarkEngine::tryAutoSaving() {
00469     if (!canSaveGameStateCurrently()) {
00470         return; // Can't save right now, try again on the next frame
00471     }
00472 
00473     _lastAutoSaveTime = _system->getMillis();
00474 
00475     // Get a thumbnail of the game screen if we don't have one already
00476     bool reuseThumbnail = StarkUserInterface->getGameWindowThumbnail() != nullptr;
00477     if (!reuseThumbnail) {
00478         StarkUserInterface->saveGameScreenThumbnail();
00479     }
00480 
00481     Common::Error result = saveGameState(0, "Autosave");
00482     if (result.getCode() != Common::kNoError) {
00483         warning("Unable to autosave: %s.", result.getDesc().c_str());
00484     }
00485 
00486     if (!reuseThumbnail) {
00487         StarkUserInterface->freeGameScreenThumbnail();
00488     }
00489 }
00490 
00491 void StarkEngine::pauseEngineIntern(bool pause) {
00492     Engine::pauseEngineIntern(pause);
00493 
00494     // This function may be called when an error occurs before the engine is fully initialized
00495     if (StarkGlobal && StarkGlobal->getLevel() && StarkGlobal->getCurrent()) {
00496         StarkGlobal->getLevel()->onEnginePause(pause);
00497         StarkGlobal->getCurrent()->getLevel()->onEnginePause(pause);
00498         StarkGlobal->getCurrent()->getLocation()->onEnginePause(pause);
00499     }
00500 
00501     if (_frameLimiter) {
00502         _frameLimiter->pause(pause);
00503     }
00504 
00505     // Grab a game screen thumbnail in case we need one when writing a save file
00506     if (StarkUserInterface && StarkUserInterface->isInGameScreen()) {
00507         if (pause) {
00508             StarkUserInterface->saveGameScreenThumbnail();
00509         } else {
00510             StarkUserInterface->freeGameScreenThumbnail();
00511         }
00512     }
00513 
00514     // The user may have moved the mouse or resized the window while the engine was paused
00515     if (!pause && StarkUserInterface) {
00516         onScreenChanged();
00517         StarkUserInterface->handleMouseMove(_eventMan->getMousePos());
00518     }
00519 }
00520 
00521 void StarkEngine::onScreenChanged() const {
00522     bool changed = StarkGfx->computeScreenViewport();
00523     if (changed) {
00524         StarkFontProvider->initFonts();
00525         StarkUserInterface->onScreenChanged();
00526     }
00527 }
00528 
00529 } // End of namespace Stark


Generated on Sat May 25 2019 05:00:54 for ResidualVM by doxygen 1.7.1
curved edge   curved edge