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


Generated on Sat Aug 8 2020 05:01:17 for ResidualVM by doxygen 1.7.1
curved edge   curved edge