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

myst3.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 "common/debug-channels.h"
00024 #include "common/events.h"
00025 #include "common/error.h"
00026 #include "common/config-manager.h"
00027 #include "common/file.h"
00028 #include "common/util.h"
00029 #include "common/textconsole.h"
00030 #include "common/translation.h"
00031 
00032 #include "gui/debugger.h"
00033 #include "gui/error.h"
00034 
00035 #include "engines/engine.h"
00036 
00037 #include "engines/myst3/archive.h"
00038 #include "engines/myst3/console.h"
00039 #include "engines/myst3/database.h"
00040 #include "engines/myst3/effects.h"
00041 #include "engines/myst3/myst3.h"
00042 #include "engines/myst3/nodecube.h"
00043 #include "engines/myst3/nodeframe.h"
00044 #include "engines/myst3/scene.h"
00045 #include "engines/myst3/state.h"
00046 #include "engines/myst3/cursor.h"
00047 #include "engines/myst3/inventory.h"
00048 #include "engines/myst3/script.h"
00049 #include "engines/myst3/menu.h"
00050 #include "engines/myst3/movie.h"
00051 #include "engines/myst3/sound.h"
00052 #include "engines/myst3/ambient.h"
00053 #include "engines/myst3/transition.h"
00054 
00055 #include "image/jpeg.h"
00056 
00057 #include "graphics/conversion.h"
00058 #include "graphics/renderer.h"
00059 #include "graphics/yuv_to_rgb.h"
00060 
00061 #include "math/vector2d.h"
00062 
00063 namespace Myst3 {
00064 
00065 Myst3Engine::Myst3Engine(OSystem *syst, const Myst3GameDescription *version) :
00066         Engine(syst), _system(syst), _gameDescription(version),
00067         _db(0), _scriptEngine(0),
00068         _state(0), _node(0), _scene(0), _archiveNode(0),
00069         _cursor(0), _inventory(0), _gfx(0), _menu(0),
00070         _rnd(0), _sound(0), _ambient(0),
00071         _inputSpacePressed(false), _inputEnterPressed(false),
00072         _inputEscapePressed(false), _inputTildePressed(false),
00073         _inputEscapePressedNotConsumed(false),
00074         _interactive(false),
00075         _menuAction(0), _projectorBackground(0),
00076         _shakeEffect(0), _rotationEffect(0),
00077         _backgroundSoundScriptLastRoomId(0),
00078         _backgroundSoundScriptLastAgeId(0),
00079         _transition(0), _frameLimiter(0), _inventoryManualHide(false) {
00080     DebugMan.addDebugChannel(kDebugVariable, "Variable", "Track Variable Accesses");
00081     DebugMan.addDebugChannel(kDebugSaveLoad, "SaveLoad", "Track Save/Load Function");
00082     DebugMan.addDebugChannel(kDebugScript, "Script", "Track Script Execution");
00083     DebugMan.addDebugChannel(kDebugNode, "Node", "Track Node Changes");
00084 
00085     // Add subdirectories to the search path to allow running from a full HDD install
00086     const Common::FSNode gameDataDir(ConfMan.get("path"));
00087     SearchMan.addSubDirectoryMatching(gameDataDir, "bin");
00088     SearchMan.addSubDirectoryMatching(gameDataDir, "M3Data");
00089     SearchMan.addSubDirectoryMatching(gameDataDir, "M3Data/TEXT");
00090     SearchMan.addSubDirectoriesMatching(gameDataDir, "EXILE Disc ?/Data", true);
00091 
00092     // Win DVD version directories
00093     SearchMan.addSubDirectoryMatching(gameDataDir, "English");
00094     SearchMan.addSubDirectoryMatching(gameDataDir, "Data");
00095 
00096     // Mac DVD version directories
00097     SearchMan.addSubDirectoryMatching(gameDataDir, "Exile DVD");
00098     SearchMan.addSubDirectoryMatching(gameDataDir, "Exile DVD/data");
00099 
00100     // PS2 version directories
00101     SearchMan.addSubDirectoryMatching(gameDataDir, "GAMEDATA");
00102     SearchMan.addSubDirectoryMatching(gameDataDir, "GAMEDATA/WORLD");
00103     SearchMan.addSubDirectoryMatching(gameDataDir, "GAMEDATA/WORLD/SOUND");
00104     SearchMan.addSubDirectoryMatching(gameDataDir, "MYST3BIN");
00105     SearchMan.addSubDirectoryMatching(gameDataDir, "MYST3BIN/DISCS");
00106     SearchMan.addSubDirectoryMatching(gameDataDir, "MYST3BIN/DISCS/DATA");
00107     SearchMan.addSubDirectoryMatching(gameDataDir, "MYST3BIN/M3DATA");
00108     SearchMan.addSubDirectoryMatching(gameDataDir, "MYST3BIN/M3DATA/TEXT");
00109     SearchMan.addSubDirectoryMatching(gameDataDir, "MYST3BIN/M3DATA/TEXT/NTSC");
00110     SearchMan.addSubDirectoryMatching(gameDataDir, "MYST3BIN/M3DATA/TEXT/PAL");
00111 }
00112 
00113 Myst3Engine::~Myst3Engine() {
00114     DebugMan.clearAllDebugChannels();
00115 
00116     closeArchives();
00117 
00118     delete _menu;
00119     delete _inventory;
00120     delete _cursor;
00121     delete _scene;
00122     delete _archiveNode;
00123     delete _db;
00124     delete _scriptEngine;
00125     delete _state;
00126     delete _rnd;
00127     delete _sound;
00128     delete _ambient;
00129     delete _frameLimiter;
00130     delete _gfx;
00131 }
00132 
00133 bool Myst3Engine::hasFeature(EngineFeature f) const {
00134     // The TinyGL renderer does not support arbitrary resolutions for now
00135     Common::String rendererConfig = ConfMan.get("renderer");
00136     Graphics::RendererType desiredRendererType = Graphics::parseRendererTypeCode(rendererConfig);
00137     Graphics::RendererType matchingRendererType = Graphics::getBestMatchingAvailableRendererType(desiredRendererType);
00138     bool softRenderer = matchingRendererType == Graphics::kRendererTypeTinyGL;
00139 
00140     return
00141         (f == kSupportsReturnToLauncher) ||
00142         (f == kSupportsLoadingDuringRuntime) ||
00143         (f == kSupportsSavingDuringRuntime) ||
00144         (f == kSupportsArbitraryResolutions && !softRenderer);
00145 }
00146 
00147 Common::Error Myst3Engine::run() {
00148     if (!checkDatafiles()) {
00149         // An error message has already been displayed
00150         return Common::kUserCanceled;
00151     }
00152 
00153     _gfx = createRenderer(_system);
00154     _gfx->init();
00155     _gfx->clear();
00156 
00157     _frameLimiter = new FrameLimiter(_system, ConfMan.getInt("engine_speed"));
00158     _sound = new Sound(this);
00159     _ambient = new Ambient(this);
00160     _rnd = new Common::RandomSource("sprint");
00161     setDebugger(new Console(this));
00162     _scriptEngine = new Script(this);
00163     _db = new Database(getPlatform(), getGameLanguage(), getGameLocalizationType());
00164     _state = new GameState(getPlatform(), _db);
00165     _scene = new Scene(this);
00166     if (getPlatform() == Common::kPlatformXbox) {
00167         _menu = new AlbumMenu(this);
00168     } else {
00169         _menu = new PagingMenu(this);
00170     }
00171     _archiveNode = new Archive();
00172 
00173     _system->showMouse(false);
00174 
00175     settingsInitDefaults();
00176     syncSoundSettings();
00177     openArchives();
00178 
00179     _cursor = new Cursor(this);
00180     _inventory = new Inventory(this);
00181 
00182 
00183     // Init the font
00184     Graphics::Surface *font = loadTexture(1206);
00185     _gfx->initFont(font);
00186     font->free();
00187     delete font;
00188 
00189     if (ConfMan.hasKey("save_slot")) {
00190         // Load game from specified slot, if any
00191         Common::Error loadError = loadGameState(ConfMan.getInt("save_slot"));
00192         if (loadError.getCode() != Common::kNoError) {
00193             return loadError;
00194         }
00195     } else {
00196         if (getPlatform() == Common::kPlatformXbox) {
00197             // Play the logo videos
00198             loadNode(kNodeLogoPlay, kLogo, 11);
00199         }
00200 
00201         // Game init script, loads the menu
00202         loadNode(kNodeSharedInit, kRoomShared, 1);
00203     }
00204 
00205     while (!shouldQuit()) {
00206         runNodeBackgroundScripts();
00207         processInput(true);
00208         updateCursor();
00209 
00210         if (_menuAction) {
00211             _menu->updateMainMenu(_menuAction);
00212             _menuAction = 0;
00213         }
00214 
00215         drawFrame();
00216     }
00217 
00218     unloadNode();
00219 
00220     _archiveNode->close();
00221     _gfx->freeFont();
00222 
00223     // Make sure the mouse is unlocked
00224     _system->lockMouse(false);
00225 
00226     return Common::kNoError;
00227 }
00228 
00229 bool Myst3Engine::addArchive(const Common::String &file, bool mandatory) {
00230     Archive *archive = new Archive();
00231     bool opened = archive->open(file.c_str(), 0);
00232 
00233     if (opened) {
00234         _archivesCommon.push_back(archive);
00235     } else {
00236         delete archive;
00237         if (mandatory)
00238             error("Unable to open archive %s", file.c_str());
00239     }
00240 
00241     return opened;
00242 }
00243 
00244 void Myst3Engine::openArchives() {
00245     // The language of the menus is always the same as the executable
00246     // The English CD version can only display English text
00247     // The non English CD versions can display their localized language and English
00248     // The DVD version can display 6 different languages
00249 
00250     Common::String menuLanguage;
00251     Common::String textLanguage;
00252 
00253     switch (getGameLanguage()) {
00254     case Common::NL_NLD:
00255         menuLanguage = "DUTCH";
00256         break;
00257     case Common::FR_FRA:
00258         menuLanguage = "FRENCH";
00259         break;
00260     case Common::DE_DEU:
00261         menuLanguage = "GERMAN";
00262         break;
00263     case Common::HE_ISR:
00264         menuLanguage = "HEBREW";
00265         break;
00266     case Common::IT_ITA:
00267         menuLanguage = "ITALIAN";
00268         break;
00269     case Common::ES_ESP:
00270         menuLanguage = "SPANISH";
00271         break;
00272     case Common::JA_JPN:
00273         menuLanguage = "JAPANESE";
00274         break;
00275     case Common::PL_POL:
00276         menuLanguage = "POLISH";
00277         break;
00278     case Common::EN_ANY:
00279     case Common::RU_RUS:
00280     default:
00281         menuLanguage = "ENGLISH";
00282         break;
00283     }
00284 
00285     if (getGameLocalizationType() == kLocMulti6) {
00286         switch (ConfMan.getInt("text_language")) {
00287         case kDutch:
00288             textLanguage = "DUTCH";
00289             break;
00290         case kFrench:
00291             textLanguage = "FRENCH";
00292             break;
00293         case kGerman:
00294             textLanguage = "GERMAN";
00295             break;
00296         case kItalian:
00297             textLanguage = "ITALIAN";
00298             break;
00299         case kSpanish:
00300             textLanguage = "SPANISH";
00301             break;
00302         case kEnglish:
00303         default:
00304             textLanguage = "ENGLISH";
00305             break;
00306         }
00307     } else if (getGameLanguage() == Common::HE_ISR) {
00308         textLanguage = "ENGLISH"; // The Hebrew version does not have a "HEBREW.m3t" file
00309     } else {
00310         if (getGameLocalizationType() == kLocMonolingual || ConfMan.getInt("text_language")) {
00311             textLanguage = menuLanguage;
00312         } else {
00313             textLanguage = "ENGLISH";
00314         }
00315     }
00316 
00317     if (getGameLocalizationType() != kLocMonolingual && getPlatform() != Common::kPlatformXbox && textLanguage == "ENGLISH") {
00318         textLanguage = "ENGLISHjp";
00319     }
00320 
00321     if (getPlatform() == Common::kPlatformXbox) {
00322         menuLanguage += "X";
00323         textLanguage += "X";
00324     }
00325 
00326     // Load all the override files in the search path
00327     Common::ArchiveMemberList overrides;
00328     SearchMan.listMatchingMembers(overrides, "*.m3o");
00329     for (Common::ArchiveMemberList::const_iterator it = overrides.begin(); it != overrides.end(); it++) {
00330         addArchive(it->get()->getName(), false);
00331     }
00332 
00333     addArchive(textLanguage + ".m3t", true);
00334 
00335     if (getGameLocalizationType() != kLocMonolingual || getPlatform() == Common::kPlatformXbox || getGameLanguage() == Common::HE_ISR) {
00336         addArchive(menuLanguage + ".m3u", true);
00337     }
00338 
00339     addArchive("RSRC.m3r", true);
00340 }
00341 
00342 bool Myst3Engine::isTextLanguageEnglish() const {
00343     if (getGameLocalizationType() == kLocMonolingual && getGameLanguage() == Common::EN_ANY) {
00344         return true;
00345     }
00346 
00347     return getGameLocalizationType() != kLocMonolingual && ConfMan.getInt("text_language") == kEnglish;
00348 }
00349 
00350 void Myst3Engine::closeArchives() {
00351     for (uint i = 0; i < _archivesCommon.size(); i++)
00352         delete _archivesCommon[i];
00353 
00354     _archivesCommon.clear();
00355 }
00356 
00357 bool Myst3Engine::checkDatafiles() {
00358     if (!SearchMan.hasFile("OVER101.m3o")) {
00359         warning("Unable to open the update game archive 'OVER101.m3o'");
00360         static const char *updateMessage =
00361                 _("This version of Myst III has not been updated with the latest official patch.\n"
00362                           "Please install the official update corresponding to your game's language.\n"
00363                           "The updates can be downloaded from:\n"
00364                           "http://www.residualvm.org/downloads/");
00365         warning("%s", updateMessage);
00366         GUI::displayErrorDialog(updateMessage);
00367         return false;
00368     }
00369 
00370     return true;
00371 }
00372 
00373 HotSpot *Myst3Engine::getHoveredHotspot(NodePtr nodeData, uint16 var) {
00374     _state->setHotspotHovered(false);
00375     _state->setHotspotActiveRect(0);
00376 
00377     if (_state->getViewType() == kCube) {
00378         float pitch, heading;
00379         _cursor->getDirection(pitch, heading);
00380 
00381         for (uint j = 0; j < nodeData->hotspots.size(); j++) {
00382             int32 hitRect = nodeData->hotspots[j].isPointInRectsCube(pitch, heading);
00383             if (hitRect >= 0 && nodeData->hotspots[j].isEnabled(_state, var)) {
00384                 if (nodeData->hotspots[j].rects.size() > 1) {
00385                     _state->setHotspotHovered(true);
00386                     _state->setHotspotActiveRect(hitRect);
00387                 }
00388                 return &nodeData->hotspots[j];
00389             }
00390         }
00391     } else {
00392         // get the mouse position in original game window coordinates
00393         Common::Point mouse = _cursor->getPosition(false);
00394         mouse = _scene->scalePoint(mouse);
00395 
00396         for (uint j = 0; j < nodeData->hotspots.size(); j++) {
00397             int32 hitRect = nodeData->hotspots[j].isPointInRectsFrame(_state, mouse);
00398             if (hitRect >= 0 && nodeData->hotspots[j].isEnabled(_state, var)) {
00399                 if (nodeData->hotspots[j].rects.size() > 1) {
00400                     _state->setHotspotHovered(true);
00401                     _state->setHotspotActiveRect(hitRect);
00402                 }
00403                 return &nodeData->hotspots[j];
00404             }
00405         }
00406     }
00407 
00408     return 0;
00409 }
00410 
00411 void Myst3Engine::updateCursor() {
00412     if (!_inventory->isMouseInside()) {
00413         _inventoryManualHide = false;
00414     }
00415 
00416     if (isInventoryVisible() && _inventory->isMouseInside()) {
00417         _inventory->updateCursor();
00418         return;
00419     }
00420 
00421     NodePtr nodeData = _db->getNodeData(_state->getLocationNode(), _state->getLocationRoom(), _state->getLocationAge());
00422 
00423     _state->setHotspotIgnoreClick(true);
00424     HotSpot *hovered = getHoveredHotspot(nodeData);
00425     _state->setHotspotIgnoreClick(false);
00426 
00427     if (hovered) {
00428         _cursor->changeCursor(hovered->cursor);
00429     } else {
00430         _cursor->changeCursor(8);
00431     }
00432 }
00433 
00434 void Myst3Engine::processInput(bool interactive) {
00435     _interactive = interactive;
00436 
00437     if (_state->hasVarGamePadUpPressed()) {
00438         // Reset the gamepad directions once they had a chance to be read by the scripts
00439         // This combined with keyboard repeat ensures the menu does not scroll too fast
00440         _state->setGamePadUpPressed(false);
00441         _state->setGamePadDownPressed(false);
00442         _state->setGamePadLeftPressed(false);
00443         _state->setGamePadRightPressed(false);
00444     }
00445 
00446     bool shouldInteractWithHoveredElement = false;
00447 
00448     // Process events
00449     Common::Event event;
00450     while (getEventManager()->pollEvent(event)) {
00451         if (_state->hasVarGamePadUpPressed()) {
00452             processEventForGamepad(event);
00453         }
00454 
00455         processEventForKeyboardState(event);
00456 
00457         if (event.type == Common::EVENT_MOUSEMOVE) {
00458             if (_state->getViewType() == kCube
00459                     && _cursor->isPositionLocked()) {
00460                 _scene->updateCamera(event.relMouse);
00461             }
00462 
00463             _cursor->updatePosition(event.mouse);
00464 
00465         } else if (event.type == Common::EVENT_LBUTTONDOWN) {
00466             shouldInteractWithHoveredElement = true;
00467         } else if (event.type == Common::EVENT_RBUTTONDOWN) {
00468             // Skip the event when in non-interactive mode
00469             if (!interactive)
00470                 continue;
00471             // Nothing to do if not in cube view
00472             if (_state->getViewType() != kCube)
00473                 continue;
00474             // Don't unlock if the cursor is transparent
00475             if (!_state->getCursorTransparency())
00476                 continue;
00477 
00478             bool cursorLocked = _cursor->isPositionLocked();
00479             _cursor->lockPosition(!cursorLocked);
00480 
00481         } else if (event.type == Common::EVENT_KEYDOWN) {
00482             // Save file name input
00483             if (_menu->handleInput(event.kbd)) {
00484                 continue;
00485             }
00486 
00487             if (event.kbdRepeat) {
00488                 // Ignore keyboard repeat except when entering save names
00489                 continue;
00490             }
00491 
00492             switch (event.kbd.keycode) {
00493             case Common::KEYCODE_ESCAPE:
00494                 _inputEscapePressedNotConsumed = true;
00495                 break;
00496             case Common::KEYCODE_RETURN:
00497             case Common::KEYCODE_KP_ENTER:
00498                 if (event.kbd.hasFlags(Common::KBD_ALT)) {
00499                     _gfx->toggleFullscreen();
00500                 } else {
00501                     shouldInteractWithHoveredElement = true;
00502                 }
00503                 break;
00504             case Common::KEYCODE_F5:
00505                 // Open main menu
00506                 if (_cursor->isVisible() && interactive) {
00507                     if (_state->getLocationRoom() != kRoomMenu)
00508                         _menu->goToNode(kNodeMenuMain);
00509                 }
00510                 break;
00511             case Common::KEYCODE_i:
00512                 if (event.kbd.flags & Common::KBD_CTRL) {
00513                     bool mouseInverted = ConfMan.getBool("mouse_inverted");
00514                     mouseInverted = !mouseInverted;
00515                     ConfMan.setBool("mouse_inverted", mouseInverted);
00516                 }
00517                 break;
00518             default:
00519                 break;
00520             }
00521         } else if (event.type == Common::EVENT_SCREEN_CHANGED) {
00522             _gfx->computeScreenViewport();
00523             _cursor->updatePosition(_eventMan->getMousePos());
00524             _inventory->reflow();
00525         }
00526     }
00527 
00528     // The input state variables need to be set before calling the scripts
00529     updateInputState();
00530 
00531     if (shouldInteractWithHoveredElement && interactive) {
00532         interactWithHoveredElement();
00533     }
00534 
00535     // Open main menu
00536     // This is not checked directly in the event handling code
00537     // because menu open requests done while in lookOnly mode
00538     // need to be honored after leaving the inner script loop,
00539     // especially when the script loop was cancelled due to pressing
00540     // escape.
00541     if (_inputEscapePressedNotConsumed && interactive) {
00542         _inputEscapePressedNotConsumed = false;
00543         if (_cursor->isVisible() && _state->hasVarMenuEscapePressed()) {
00544             if (_state->getLocationRoom() != kRoomMenu)
00545                 _menu->goToNode(kNodeMenuMain);
00546             else
00547                 _state->setMenuEscapePressed(1);
00548         }
00549     }
00550 }
00551 
00552 void Myst3Engine::processEventForKeyboardState(const Common::Event &event) {
00553     if (event.type == Common::EVENT_KEYDOWN) {
00554         if (event.kbdRepeat) {
00555             // Ignore keyboard repeat except when entering save names
00556             return;
00557         }
00558 
00559         switch (event.kbd.keycode) {
00560             case Common::KEYCODE_ESCAPE:
00561                 _inputEscapePressed = true;
00562                 break;
00563             case Common::KEYCODE_RETURN:
00564             case Common::KEYCODE_KP_ENTER:
00565                 if (!event.kbd.hasFlags(Common::KBD_ALT)) {
00566                     _inputEnterPressed = true;
00567                 }
00568                 break;
00569             case Common::KEYCODE_SPACE:
00570                 _inputSpacePressed = true;
00571                 break;
00572             case Common::KEYCODE_BACKQUOTE: // tilde, used to trigger the easter eggs
00573                 _inputTildePressed = true;
00574                 break;
00575             default:
00576                 break;
00577         }
00578     } else if (event.type == Common::EVENT_KEYUP) {
00579         switch (event.kbd.keycode) {
00580             case Common::KEYCODE_ESCAPE:
00581                 _inputEscapePressed = false;
00582                 _inputEscapePressedNotConsumed = false;
00583                 break;
00584             case Common::KEYCODE_RETURN:
00585             case Common::KEYCODE_KP_ENTER:
00586                 _inputEnterPressed = false;
00587                 break;
00588             case Common::KEYCODE_SPACE:
00589                 _inputSpacePressed = false;
00590                 break;
00591             case Common::KEYCODE_BACKQUOTE:
00592                 _inputTildePressed = false;
00593                 break;
00594             default:
00595                 break;
00596         }
00597     }
00598 }
00599 
00600 void Myst3Engine::processEventForGamepad(const Common::Event &event) {
00601     if (event.type == Common::EVENT_LBUTTONDOWN) {
00602         _state->setGamePadActionPressed(true);
00603     } else if (event.type == Common::EVENT_LBUTTONUP) {
00604         _state->setGamePadActionPressed(false);
00605     } else if (event.type == Common::EVENT_KEYDOWN) {
00606         if (event.kbdRepeat) return;
00607 
00608         switch (event.kbd.keycode) {
00609         case Common::KEYCODE_RETURN:
00610         case Common::KEYCODE_KP_ENTER:
00611             _state->setGamePadActionPressed(true);
00612             break;
00613         case Common::KEYCODE_UP:
00614             _state->setGamePadUpPressed(true);
00615             break;
00616         case Common::KEYCODE_DOWN:
00617             _state->setGamePadDownPressed(true);
00618             break;
00619         case Common::KEYCODE_LEFT:
00620             _state->setGamePadLeftPressed(true);
00621             break;
00622         case Common::KEYCODE_RIGHT:
00623             _state->setGamePadRightPressed(true);
00624             break;
00625         case Common::KEYCODE_ESCAPE:
00626             _state->setGamePadCancelPressed(true);
00627             break;
00628         default:
00629             break;
00630         }
00631     } else if (event.type == Common::EVENT_KEYUP) {
00632         switch (event.kbd.keycode) {
00633         case Common::KEYCODE_RETURN:
00634         case Common::KEYCODE_KP_ENTER:
00635             _state->setGamePadActionPressed(false);
00636             break;
00637         case Common::KEYCODE_ESCAPE:
00638             _state->setGamePadCancelPressed(false);
00639             break;
00640         default:
00641             break;
00642         }
00643     }
00644 }
00645 
00646 void Myst3Engine::updateInputState() {
00647     _state->setInputMousePressed(inputValidatePressed());
00648     _state->setInputTildePressed(_inputTildePressed);
00649     _state->setInputSpacePressed(_inputSpacePressed);
00650     _state->setInputEscapePressed(_inputEscapePressed);
00651 }
00652 
00653 void Myst3Engine::interactWithHoveredElement() {
00654     if (isInventoryVisible() && _inventory->isMouseInside()) {
00655         uint16 hoveredInventory = _inventory->hoveredItem();
00656         if (hoveredInventory > 0) {
00657             _inventory->useItem(hoveredInventory);
00658         } else {
00659             if (isWideScreenModEnabled()) {
00660                 _inventoryManualHide = true;
00661             }
00662         }
00663         return;
00664     }
00665 
00666     NodePtr nodeData = _db->getNodeData(_state->getLocationNode(), _state->getLocationRoom(), _state->getLocationAge());
00667     HotSpot *hovered = getHoveredHotspot(nodeData);
00668 
00669     if (hovered) {
00670         _scriptEngine->run(&hovered->script);
00671         return;
00672     }
00673 
00674     // Bad click
00675     _sound->playEffect(697, 5);
00676 }
00677 
00678 void Myst3Engine::drawFrame(bool noSwap) {
00679     _sound->update();
00680     _gfx->clear();
00681 
00682     if (_state->getViewType() == kCube) {
00683         float pitch = _state->getLookAtPitch();
00684         float heading = _state->getLookAtHeading();
00685         float fov = _state->getLookAtFOV();
00686 
00687         // Apply the rotation effect
00688         if (_rotationEffect) {
00689             _rotationEffect->update();
00690 
00691             heading += _rotationEffect->getHeadingOffset();
00692             _state->lookAt(pitch, heading);
00693         }
00694 
00695         // Apply the shake effect
00696         if (_shakeEffect) {
00697             _shakeEffect->update();
00698             pitch += _shakeEffect->getPitchOffset();
00699             heading += _shakeEffect->getHeadingOffset();
00700         }
00701 
00702         _gfx->setupCameraPerspective(pitch, heading, fov);
00703     }
00704 
00705     if (_node) {
00706         _node->update();
00707         _gfx->renderDrawable(_node, _scene);
00708     }
00709 
00710     for (int i = _movies.size() - 1; i >= 0 ; i--) {
00711         _movies[i]->update();
00712         _gfx->renderDrawable(_movies[i], _scene);
00713     }
00714 
00715     if (_state->getViewType() == kMenu) {
00716         _gfx->renderDrawable(_menu, _scene);
00717     }
00718 
00719     for (uint i = 0; i < _drawables.size(); i++) {
00720         _gfx->renderDrawable(_drawables[i], _scene);
00721     }
00722 
00723     if (_state->getViewType() != kMenu) {
00724         float pitch = _state->getLookAtPitch();
00725         float heading = _state->getLookAtHeading();
00726         SunSpot flare = computeSunspotsIntensity(pitch, heading);
00727         if (flare.intensity >= 0)
00728             _scene->drawSunspotFlare(flare);
00729     }
00730 
00731     if (isInventoryVisible()) {
00732         _gfx->renderWindow(_inventory);
00733     }
00734 
00735     // Draw overlay 2D movies
00736     for (int i = _movies.size() - 1; i >= 0 ; i--) {
00737         _gfx->renderDrawableOverlay(_movies[i], _scene);
00738     }
00739 
00740     for (uint i = 0; i < _drawables.size(); i++) {
00741         _gfx->renderDrawableOverlay(_drawables[i], _scene);
00742     }
00743 
00744     // Draw spot subtitles
00745     if (_node) {
00746         _gfx->renderDrawableOverlay(_node, _scene);
00747     }
00748 
00749     bool cursorVisible = _cursor->isVisible();
00750 
00751     if (getPlatform() == Common::kPlatformXbox) {
00752         // The cursor is not drawn in the Xbox version menus and journals
00753         cursorVisible &= !(_state->getLocationRoom() == kRoomMenu || _state->getLocationRoom() == kRoomJournals);
00754     }
00755 
00756     if (cursorVisible)
00757         _gfx->renderDrawable(_cursor, _scene);
00758 
00759     _gfx->flipBuffer();
00760 
00761     if (!noSwap) {
00762         _frameLimiter->delayBeforeSwap();
00763         _system->updateScreen();
00764         _state->updateFrameCounters();
00765         _frameLimiter->startFrame();
00766     }
00767 }
00768 
00769 bool Myst3Engine::isInventoryVisible() {
00770     if (_state->getViewType() == kMenu)
00771         return false;
00772 
00773     if (_node && _node->hasSubtitlesToDraw())
00774         return false;
00775 
00776     if (_inventoryManualHide) {
00777         return false;
00778     }
00779 
00780     // Only draw the inventory when the mouse is inside its area
00781     if (isWideScreenModEnabled() && !_inventory->isMouseInside()) {
00782         return false;
00783     }
00784 
00785     return true;
00786 }
00787 
00788 void Myst3Engine::setupTransition() {
00789     delete _transition;
00790     _transition = new Transition(this);
00791 }
00792 
00793 void Myst3Engine::drawTransition(TransitionType transitionType) {
00794     if (_transition) {
00795         _interactive = false; // Don't allow loading while drawing transitions
00796         _transition->draw(transitionType);
00797         delete _transition;
00798         _transition = nullptr;
00799     }
00800 }
00801 
00802 void Myst3Engine::goToNode(uint16 nodeID, TransitionType transitionType) {
00803     uint16 node = _state->getLocationNextNode();
00804     if (!node)
00805         node = nodeID;
00806 
00807     uint16 room = _state->getLocationNextRoom();
00808     uint16 age = _state->getLocationNextAge();
00809 
00810     setupTransition();
00811 
00812     ViewType sourceViewType = _state->getViewType();
00813     if (sourceViewType == kCube) {
00814         // The lookat direction in the next node should be
00815         // the direction of the mouse cursor
00816         float pitch, heading;
00817         _cursor->getDirection(pitch, heading);
00818         _state->lookAt(pitch, heading);
00819     }
00820 
00821     loadNode(node, room, age);
00822 
00823     _state->setLocationNextNode(0);
00824     _state->setLocationNextRoom(0);
00825     _state->setLocationNextAge(0);
00826 
00827     if (_state->getAmbiantPreviousFadeOutDelay() > 0) {
00828         _ambient->playCurrentNode(100, _state->getAmbiantPreviousFadeOutDelay());
00829     }
00830 
00831     drawTransition(transitionType);
00832 }
00833 
00834 void Myst3Engine::loadNode(uint16 nodeID, uint32 roomID, uint32 ageID) {
00835     unloadNode();
00836 
00837     _scriptEngine->run(&_db->getNodeInitScript());
00838 
00839     if (nodeID)
00840         _state->setLocationNode(_state->valueOrVarValue(nodeID));
00841 
00842     if (roomID)
00843         _state->setLocationRoom(_state->valueOrVarValue(roomID));
00844     else
00845         roomID = _state->getLocationRoom();
00846 
00847     if (ageID)
00848         _state->setLocationAge(_state->valueOrVarValue(ageID));
00849     else
00850         ageID = _state->getLocationAge();
00851 
00852     _db->cacheRoom(roomID, ageID);
00853 
00854     Common::String newRoomName = _db->getRoomName(roomID, ageID);
00855     if ((!_archiveNode || _archiveNode->getRoomName() != newRoomName) && !_db->isCommonRoom(roomID, ageID)) {
00856 
00857         Common::String nodeFile = Common::String::format("%snodes.m3a", newRoomName.c_str());
00858 
00859         _archiveNode->close();
00860         if (!_archiveNode->open(nodeFile.c_str(), newRoomName.c_str())) {
00861             error("Unable to open archive %s", nodeFile.c_str());
00862         }
00863     }
00864 
00865     runNodeInitScripts();
00866     if (!_node) {
00867         return; // The main init script does not load a node
00868     }
00869 
00870     // The effects can only be created after running the node init scripts
00871     _node->initEffects();
00872     _shakeEffect = ShakeEffect::create(this);
00873     _rotationEffect = RotationEffect::create(this);
00874 
00875     // WORKAROUND: In Narayan, the scripts in node NACH 9 test on var 39
00876     // without first reinitializing it leading to Saavedro not always giving
00877     // Releeshan to the player when he is trapped between both shields.
00878     if (nodeID == 9 && roomID == kRoomNarayan)
00879         _state->setVar(39, 0);
00880 }
00881 
00882 void Myst3Engine::unloadNode() {
00883     if (!_node)
00884         return;
00885 
00886     // Delete all movies
00887     removeMovie(0);
00888 
00889     // Remove all sunspots
00890     for (uint i = 0; i < _sunspots.size(); i++)
00891         delete _sunspots[i];
00892 
00893     _sunspots.clear();
00894 
00895     // Clean up the effects
00896     delete _shakeEffect;
00897     _shakeEffect = nullptr;
00898     _state->setShakeEffectAmpl(0);
00899     delete _rotationEffect;
00900     _rotationEffect = nullptr;
00901 
00902     delete _node;
00903     _node = nullptr;
00904 }
00905 
00906 void Myst3Engine::runNodeInitScripts() {
00907     NodePtr nodeData = _db->getNodeData(
00908             _state->getLocationNode(),
00909             _state->getLocationRoom(),
00910             _state->getLocationAge());
00911 
00912     NodePtr nodeDataInit = _db->getNodeData(
00913             32765,
00914             _state->getLocationRoom(),
00915             _state->getLocationAge());
00916 
00917     if (nodeDataInit)
00918         runScriptsFromNode(32765);
00919 
00920     if (!nodeData)
00921         error("Node %d unknown in the database", _state->getLocationNode());
00922 
00923     for (uint j = 0; j < nodeData->scripts.size(); j++) {
00924         if (_state->evaluate(nodeData->scripts[j].condition)) {
00925             _scriptEngine->run(&nodeData->scripts[j].script);
00926         }
00927     }
00928 
00929     // Mark the node as a reachable zip destination
00930     _state->markNodeAsVisited(
00931             _state->getLocationNode(),
00932             _state->getLocationRoom(),
00933             _state->getLocationAge());
00934 }
00935 
00936 void Myst3Engine::runNodeBackgroundScripts() {
00937     NodePtr nodeDataRoom = _db->getNodeData(32765, _state->getLocationRoom(), _state->getLocationAge());
00938 
00939     if (nodeDataRoom) {
00940         for (uint j = 0; j < nodeDataRoom->hotspots.size(); j++) {
00941             if (nodeDataRoom->hotspots[j].condition == -1) {
00942                 if (!_scriptEngine->run(&nodeDataRoom->hotspots[j].script))
00943                     break;
00944             }
00945         }
00946     }
00947 
00948     NodePtr nodeData = _db->getNodeData(_state->getLocationNode(), _state->getLocationRoom(), _state->getLocationAge());
00949 
00950     for (uint j = 0; j < nodeData->hotspots.size(); j++) {
00951         if (nodeData->hotspots[j].condition == -1) {
00952             if (!_scriptEngine->run(&nodeData->hotspots[j].script))
00953                 break;
00954         }
00955     }
00956 }
00957 
00958 void Myst3Engine::loadNodeCubeFaces(uint16 nodeID) {
00959     _state->setViewType(kCube);
00960 
00961     _cursor->lockPosition(true);
00962     updateCursor();
00963 
00964     _node = new NodeCube(this, nodeID);
00965 }
00966 
00967 void Myst3Engine::loadNodeFrame(uint16 nodeID) {
00968     _state->setViewType(kFrame);
00969 
00970     _cursor->lockPosition(false);
00971     updateCursor();
00972 
00973     _node = new NodeFrame(this, nodeID);
00974 }
00975 
00976 void Myst3Engine::loadNodeMenu(uint16 nodeID) {
00977     _state->setViewType(kMenu);
00978 
00979     _cursor->lockPosition(false);
00980     updateCursor();
00981 
00982     _node = new NodeFrame(this, nodeID);
00983 }
00984 
00985 void Myst3Engine::runScriptsFromNode(uint16 nodeID, uint32 roomID, uint32 ageID) {
00986     if (roomID == 0)
00987         roomID = _state->getLocationRoom();
00988 
00989     if (ageID == 0)
00990         ageID = _state->getLocationAge();
00991 
00992     NodePtr nodeData = _db->getNodeData(nodeID, roomID, ageID);
00993 
00994     for (uint j = 0; j < nodeData->scripts.size(); j++) {
00995         if (_state->evaluate(nodeData->scripts[j].condition)) {
00996             if (!_scriptEngine->run(&nodeData->scripts[j].script))
00997                 break;
00998         }
00999     }
01000 }
01001 
01002 void Myst3Engine::runBackgroundSoundScriptsFromNode(uint16 nodeID, uint32 roomID, uint32 ageID) {
01003     if (_state->getSoundScriptsSuspended())
01004         return;
01005 
01006     if (roomID == 0)
01007         roomID = _state->getLocationRoom();
01008 
01009     if (ageID == 0)
01010         ageID = _state->getLocationAge();
01011 
01012     NodePtr nodeData = _db->getNodeData(nodeID, roomID, ageID);
01013 
01014     if (!nodeData) return;
01015 
01016     if (_backgroundSoundScriptLastRoomId != roomID || _backgroundSoundScriptLastAgeId != ageID) {
01017         bool sameScript;
01018         if (   _backgroundSoundScriptLastRoomId != 0 && roomID != 0
01019             && _backgroundSoundScriptLastAgeId  != 0 && ageID  != 0) {
01020             sameScript = _db->areRoomsScriptsEqual(_backgroundSoundScriptLastRoomId, _backgroundSoundScriptLastAgeId,
01021                                                    roomID, ageID, kScriptTypeBackgroundSound);
01022         } else {
01023             sameScript = false;
01024         }
01025 
01026         // Stop previous music when the music script changes
01027         if (!sameScript
01028             && _backgroundSoundScriptLastRoomId != kRoomMenu
01029             && _backgroundSoundScriptLastRoomId != kRoomJournals
01030             && roomID != kRoomMenu
01031             && roomID != kRoomJournals) {
01032 
01033             _sound->stopMusic(_state->getSoundScriptFadeOutDelay());
01034 
01035             if (!nodeData->backgroundSoundScripts.empty()) {
01036                 _state->setSoundScriptsPaused(1);
01037                 _state->setSoundScriptsTimer(0);
01038             }
01039         }
01040 
01041         _backgroundSoundScriptLastRoomId = roomID;
01042         _backgroundSoundScriptLastAgeId  = ageID;
01043     }
01044 
01045     for (uint j = 0; j < nodeData->backgroundSoundScripts.size(); j++) {
01046         if (_state->evaluate(nodeData->backgroundSoundScripts[j].condition)) {
01047             if (!_scriptEngine->run(&nodeData->backgroundSoundScripts[j].script))
01048                 break;
01049         }
01050     }
01051 }
01052 
01053 void Myst3Engine::runAmbientScripts(uint32 node) {
01054     uint32 room = _ambient->_scriptRoom;
01055     uint32 age = _ambient->_scriptAge;
01056 
01057     if (room == 0)
01058         room = _state->getLocationRoom();
01059 
01060     if (age == 0)
01061         age = _state->getLocationAge();
01062 
01063     NodePtr nodeData = _db->getNodeData(node, room, age);
01064 
01065     if (!nodeData) return;
01066 
01067     for (uint j = 0; j < nodeData->soundScripts.size(); j++)
01068         if (_state->evaluate(nodeData->soundScripts[j].condition))
01069             _scriptEngine->run(&nodeData->soundScripts[j].script);
01070 }
01071 
01072 void Myst3Engine::loadMovie(uint16 id, uint16 condition, bool resetCond, bool loop) {
01073     ScriptedMovie *movie;
01074 
01075     if (!_state->getMovieUseBackground()) {
01076         movie = new ScriptedMovie(this, id);
01077     } else {
01078         movie = new ProjectorMovie(this, id, _projectorBackground);
01079         _projectorBackground = 0;
01080         _state->setMovieUseBackground(0);
01081     }
01082 
01083     movie->setCondition(condition);
01084     movie->setDisableWhenComplete(resetCond);
01085     movie->setLoop(loop);
01086 
01087     if (_state->getMovieScriptDriven()) {
01088         movie->setScriptDriven(_state->getMovieScriptDriven());
01089         _state->setMovieScriptDriven(0);
01090     }
01091 
01092     if (_state->getMovieStartFrameVar()) {
01093         movie->setStartFrameVar(_state->getMovieStartFrameVar());
01094         _state->setMovieStartFrameVar(0);
01095     }
01096 
01097     if (_state->getMovieEndFrameVar()) {
01098         movie->setEndFrameVar(_state->getMovieEndFrameVar());
01099         _state->setMovieEndFrameVar(0);
01100     }
01101 
01102     if (_state->getMovieStartFrame()) {
01103         movie->setStartFrame(_state->getMovieStartFrame());
01104         _state->setMovieStartFrame(0);
01105     }
01106 
01107     if (_state->getMovieEndFrame()) {
01108         movie->setEndFrame(_state->getMovieEndFrame());
01109         _state->setMovieEndFrame(0);
01110     }
01111 
01112     if (_state->getMovieNextFrameGetVar()) {
01113         movie->setNextFrameReadVar(_state->getMovieNextFrameGetVar());
01114         _state->setMovieNextFrameGetVar(0);
01115     }
01116 
01117     if (_state->getMovieNextFrameSetVar()) {
01118         movie->setNextFrameWriteVar(_state->getMovieNextFrameSetVar());
01119         _state->setMovieNextFrameSetVar(0);
01120     }
01121 
01122     if (_state->getMoviePlayingVar()) {
01123         movie->setPlayingVar(_state->getMoviePlayingVar());
01124         _state->setMoviePlayingVar(0);
01125     }
01126 
01127     if (_state->getMovieOverridePosition()) {
01128         movie->setPosU(_state->getMovieOverridePosU());
01129         movie->setPosV(_state->getMovieOverridePosV());
01130         _state->setMovieOverridePosition(0);
01131     }
01132 
01133     if (_state->getMovieUVar()) {
01134         movie->setPosUVar(_state->getMovieUVar());
01135         _state->setMovieUVar(0);
01136     }
01137 
01138     if (_state->getMovieVVar()) {
01139         movie->setPosVVar(_state->getMovieVVar());
01140         _state->setMovieVVar(0);
01141     }
01142 
01143     if (_state->getMovieOverrideCondition()) {
01144         movie->setCondition(_state->getMovieOverrideCondition());
01145         _state->setMovieOverrideCondition(0);
01146     }
01147 
01148     if (_state->getMovieConditionBit()) {
01149         movie->setConditionBit(_state->getMovieConditionBit());
01150         _state->setMovieConditionBit(0);
01151     }
01152 
01153     if (_state->getMovieForce2d()) {
01154         movie->setForce2d(_state->getMovieForce2d());
01155         _state->setMovieForce2d(0);
01156     }
01157 
01158     if (_state->getMovieVolume1()) {
01159         movie->setVolume(_state->getMovieVolume1());
01160         _state->setMovieVolume1(0);
01161     } else {
01162         movie->setVolume(_state->getMovieVolume2());
01163     }
01164 
01165     if (_state->getMovieVolumeVar()) {
01166         movie->setVolumeVar(_state->getMovieVolumeVar());
01167         _state->setMovieVolumeVar(0);
01168     }
01169 
01170     if (_state->getMovieSoundHeading()) {
01171         movie->setSoundHeading(_state->getMovieSoundHeading());
01172         _state->setMovieSoundHeading(0);
01173     }
01174 
01175     if (_state->getMoviePanningStrenght()) {
01176         movie->setSoundAttenuation(_state->getMoviePanningStrenght());
01177         _state->setMoviePanningStrenght(0);
01178     }
01179 
01180     if (_state->getMovieAdditiveBlending()) {
01181         movie->setAdditiveBlending(true);
01182         _state->setMovieAdditiveBlending(0);
01183     }
01184 
01185     if (_state->getMovieTransparency()) {
01186         movie->setTransparency(_state->getMovieTransparency());
01187         _state->setMovieTransparency(0);
01188     } else {
01189         movie->setTransparency(100);
01190     }
01191 
01192     if (_state->getMovieTransparencyVar()) {
01193         movie->setTransparencyVar(_state->getMovieTransparencyVar());
01194         _state->setMovieTransparencyVar(0);
01195     }
01196 
01197     _movies.push_back(movie);
01198 }
01199 
01200 void Myst3Engine::playSimpleMovie(uint16 id, bool fullframe, bool refreshAmbientSounds) {
01201     SimpleMovie movie(this, id);
01202 
01203     if (!movie.isVideoLoaded()) {
01204         // The video was not loaded and it was optional, just do nothing
01205         return;
01206     }
01207 
01208     if (_state->getMovieSynchronized()) {
01209         movie.setSynchronized(_state->getMovieSynchronized());
01210         _state->setMovieSynchronized(0);
01211     }
01212 
01213     if (_state->getMovieStartFrame()) {
01214         movie.setStartFrame(_state->getMovieStartFrame());
01215         _state->setMovieStartFrame(0);
01216     }
01217 
01218     if (_state->getMovieEndFrame()) {
01219         movie.setEndFrame(_state->getMovieEndFrame());
01220         _state->setMovieEndFrame(0);
01221     }
01222 
01223     if (_state->getMovieVolume1()) {
01224         movie.setVolume(_state->getMovieVolume1());
01225         _state->setMovieVolume1(0);
01226     } else {
01227         movie.setVolume(_state->getMovieVolume2());
01228     }
01229 
01230     if (fullframe) {
01231         movie.setForce2d(_state->getViewType() == kCube);
01232         movie.setForceOpaque(true);
01233         movie.setPosU(0);
01234         movie.setPosV(_state->getViewType() == kMenu ? Renderer::kTopBorderHeight : 0);
01235     }
01236 
01237     movie.play();
01238 
01239     if (refreshAmbientSounds) {
01240         movie.refreshAmbientSounds();
01241     }
01242 
01243     _drawables.push_back(&movie);
01244 
01245     while (!shouldQuit() && !movie.endOfVideo()) {
01246         movie.update();
01247 
01248         // Process events
01249         processInput(false);
01250 
01251         // Handle skipping
01252         if (_inputSpacePressed || _inputEscapePressed) {
01253             // Consume the escape key press so the menu does not open
01254             _inputEscapePressedNotConsumed = false;
01255             break;
01256         }
01257 
01258         drawFrame();
01259     }
01260 
01261     _drawables.pop_back();
01262 
01263     // Reset the movie script so that the next movie will not try to run them
01264     // when the user has skipped this one before the script is triggered.
01265     _state->setMovieScriptStartFrame(0);
01266     _state->setMovieScript(0);
01267     _state->setMovieAmbiantScriptStartFrame(0);
01268     _state->setMovieAmbiantScript(0);
01269 }
01270 
01271 void Myst3Engine::removeMovie(uint16 id) {
01272     if (id == 0) {
01273         for (uint i = 0; i < _movies.size(); i++)
01274             delete _movies[i];
01275 
01276         _movies.clear();
01277         return;
01278     } else {
01279         for (uint i = 0; i < _movies.size(); i++)
01280             if (_movies[i]->getId() == id) {
01281                 delete _movies[i];
01282                 _movies.remove_at(i);
01283                 break;
01284             }
01285     }
01286 }
01287 
01288 void Myst3Engine::setMovieLooping(uint16 id, bool loop) {
01289     for (uint i = 0; i < _movies.size(); i++) {
01290         if (_movies[i]->getId() == id) {
01291             // Enable or disable looping
01292             _movies[i]->setLoop(loop);
01293             _movies[i]->setDisableWhenComplete(!loop);
01294             break;
01295         }
01296     }
01297 }
01298 
01299 void Myst3Engine::addSpotItem(uint16 id, int16 condition, bool fade) {
01300     assert(_node);
01301 
01302     _node->loadSpotItem(id, condition, fade);
01303 }
01304 
01305 SpotItemFace *Myst3Engine::addMenuSpotItem(uint16 id, int16 condition, const Common::Rect &rect) {
01306     assert(_node);
01307 
01308     SpotItemFace *face = _node->loadMenuSpotItem(condition, rect);
01309 
01310     _menu->setSaveLoadSpotItem(id, face);
01311 
01312     return face;
01313 }
01314 
01315 void Myst3Engine::loadNodeSubtitles(uint32 id) {
01316     assert(_node);
01317 
01318     _node->loadSubtitles(id);
01319 }
01320 
01321 ResourceDescription Myst3Engine::getFileDescription(const Common::String &room, uint32 index, uint16 face,
01322                                                     Archive::ResourceType type) {
01323     Common::String archiveRoom = room;
01324     if (archiveRoom == "") {
01325         archiveRoom = _db->getRoomName(_state->getLocationRoom(), _state->getLocationAge());
01326     }
01327 
01328     ResourceDescription desc;
01329 
01330     // Search common archives
01331     uint i = 0;
01332     while (!desc.isValid() && i < _archivesCommon.size()) {
01333         desc = _archivesCommon[i]->getDescription(archiveRoom, index, face, type);
01334         i++;
01335     }
01336 
01337     // Search currently loaded node archive
01338     if (!desc.isValid() && _archiveNode)
01339         desc = _archiveNode->getDescription(archiveRoom, index, face, type);
01340 
01341     return desc;
01342 }
01343 
01344 ResourceDescriptionArray Myst3Engine::listFilesMatching(const Common::String &room, uint32 index, uint16 face,
01345                                                      Archive::ResourceType type) {
01346     Common::String archiveRoom = room;
01347     if (archiveRoom == "") {
01348         archiveRoom = _db->getRoomName(_state->getLocationRoom(), _state->getLocationAge());
01349     }
01350 
01351     for (uint i = 0; i < _archivesCommon.size(); i++) {
01352         ResourceDescriptionArray list = _archivesCommon[i]->listFilesMatching(archiveRoom, index, face, type);
01353         if (!list.empty()) {
01354             return list;
01355         }
01356     }
01357 
01358     return _archiveNode->listFilesMatching(archiveRoom, index, face, type);
01359 }
01360 
01361 Graphics::Surface *Myst3Engine::loadTexture(uint16 id) {
01362     ResourceDescription desc = getFileDescription("GLOB", id, 0, Archive::kRawData);
01363 
01364     if (!desc.isValid())
01365         error("Texture %d does not exist", id);
01366 
01367     Common::SeekableReadStream *data = desc.getData();
01368 
01369     uint32 magic = data->readUint32LE();
01370     if (magic != MKTAG('.', 'T', 'E', 'X'))
01371         error("Wrong texture format %d", id);
01372 
01373     data->readUint32LE(); // unk 1
01374     uint32 width = data->readUint32LE();
01375     uint32 height = data->readUint32LE();
01376     data->readUint32LE(); // unk 2
01377     data->readUint32LE(); // unk 3
01378 
01379 #ifdef SCUMM_BIG_ENDIAN
01380     Graphics::PixelFormat onDiskFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 24, 16, 8);
01381 #else
01382     Graphics::PixelFormat onDiskFormat = Graphics::PixelFormat(4, 8, 8, 8, 8, 8, 16, 24, 0);
01383 #endif
01384 
01385     Graphics::Surface *s = new Graphics::Surface();
01386     s->create(width, height, onDiskFormat);
01387 
01388     data->read(s->getPixels(), height * s->pitch);
01389     delete data;
01390 
01391     s->convertToInPlace(Texture::getRGBAPixelFormat());
01392 
01393     return s;
01394 }
01395 
01396 Graphics::Surface *Myst3Engine::decodeJpeg(const ResourceDescription *jpegDesc) {
01397     Common::SeekableReadStream *jpegStream = jpegDesc->getData();
01398 
01399     Image::JPEGDecoder jpeg;
01400     jpeg.setOutputPixelFormat(Texture::getRGBAPixelFormat());
01401 
01402     if (!jpeg.loadStream(*jpegStream))
01403         error("Could not decode Myst III JPEG");
01404     delete jpegStream;
01405 
01406     const Graphics::Surface *bitmap = jpeg.getSurface();
01407     assert(bitmap->format == Texture::getRGBAPixelFormat());
01408 
01409     // JPEGDecoder owns the decoded surface, we have to make a copy...
01410     Graphics::Surface *rgbaSurface = new Graphics::Surface();
01411     rgbaSurface->copyFrom(*bitmap);
01412     return rgbaSurface;
01413 }
01414 
01415 int16 Myst3Engine::openDialog(uint16 id) {
01416     Dialog *dialog;
01417 
01418     if (getPlatform() == Common::kPlatformXbox) {
01419         dialog = new GamepadDialog(this, id);
01420     } else {
01421         dialog = new ButtonsDialog(this, id);
01422     }
01423 
01424     _drawables.push_back(dialog);
01425 
01426     int16 result = -2;
01427 
01428     while (result == -2 && !shouldQuit()) {
01429         result = dialog->update();
01430         drawFrame();
01431     }
01432 
01433     _drawables.pop_back();
01434 
01435     delete dialog;
01436 
01437     return result;
01438 }
01439 
01440 void Myst3Engine::dragSymbol(uint16 var, uint16 id) {
01441     DragItem drag(this, id);
01442 
01443     _drawables.push_back(&drag);
01444 
01445     _cursor->changeCursor(2);
01446     _state->setVar(var, -1);
01447 
01448     NodePtr nodeData = _db->getNodeData(_state->getLocationNode(), _state->getLocationRoom(), _state->getLocationAge());
01449 
01450     while (inputValidatePressed() && !shouldQuit()) {
01451         processInput(false);
01452 
01453         HotSpot *hovered = getHoveredHotspot(nodeData, var);
01454         drag.setFrame(hovered ? 2 : 1);
01455 
01456         drawFrame();
01457     }
01458 
01459     _state->setVar(var, 1);
01460     _drawables.pop_back();
01461 
01462     HotSpot *hovered = getHoveredHotspot(nodeData, var);
01463     if (hovered) {
01464         _cursor->setVisible(false);
01465         _scriptEngine->run(&hovered->script);
01466         _cursor->setVisible(true);
01467     }
01468 }
01469 
01470 void Myst3Engine::dragItem(uint16 statusVar, uint16 movie, uint16 frame, uint16 hoverFrame, uint16 itemVar) {
01471     DragItem drag(this, movie);
01472 
01473     _drawables.push_back(&drag);
01474 
01475     _cursor->changeCursor(2);
01476     _state->setVar(statusVar, 0);
01477     _state->setVar(itemVar, 1);
01478 
01479     NodePtr nodeData = _db->getNodeData(_state->getLocationNode(), _state->getLocationRoom(), _state->getLocationAge());
01480 
01481     while (inputValidatePressed() && !shouldQuit()) {
01482         processInput(false);
01483 
01484         HotSpot *hovered = getHoveredHotspot(nodeData, itemVar);
01485         drag.setFrame(hovered ? hoverFrame : frame);
01486 
01487         drawFrame();
01488     }
01489 
01490     _drawables.pop_back();
01491 
01492     HotSpot *hovered = getHoveredHotspot(nodeData, itemVar);
01493     if (hovered) {
01494         _cursor->setVisible(false);
01495         _scriptEngine->run(&hovered->script);
01496         _cursor->setVisible(true);
01497     } else {
01498         _state->setVar(statusVar, 1);
01499         _state->setVar(itemVar, 0);
01500     }
01501 }
01502 
01503 bool Myst3Engine::canSaveGameStateCurrently() {
01504     bool inMenuWithNoGameLoaded = _state->getLocationRoom() == kRoomMenu && _state->getMenuSavedAge() == 0;
01505     return canLoadGameStateCurrently() && !inMenuWithNoGameLoaded && _cursor->isVisible();
01506 }
01507 
01508 bool Myst3Engine::canLoadGameStateCurrently() {
01509     // Loading from the GMM is only possible when the game is interactive
01510     // This is to prevent loading from inner loops. Loading while
01511     // in an inner loop can cause the exit condition to never happen,
01512     // or can unload required resources.
01513     return _interactive;
01514 }
01515 
01516 Common::Error Myst3Engine::loadGameState(int slot) {
01517     Common::StringArray filenames = Saves::list(_saveFileMan, getPlatform());
01518     return loadGameState(filenames[slot], kTransitionNone);
01519 }
01520 
01521 Common::Error Myst3Engine::loadGameState(Common::String fileName, TransitionType transition) {
01522     Common::SharedPtr<Common::InSaveFile> saveFile = Common::SharedPtr<Common::InSaveFile>(_saveFileMan->openForLoading(fileName));
01523     if (!saveFile) {
01524         return Common::kReadingFailed;
01525     }
01526 
01527     Common::Error loadError = _state->load(saveFile.get());
01528     if (loadError.getCode() != Common::kNoError) {
01529         return loadError;
01530     }
01531 
01532     if (saveFile->eos()) {
01533         warning("Unexpected end of file reached when reading '%s'", fileName.c_str());
01534         return Common::kReadingFailed;
01535     }
01536 
01537     if (saveFile->err()) {
01538         warning("An error occured when reading '%s'", fileName.c_str());
01539         return Common::kReadingFailed;
01540     }
01541 
01542     _inventory->loadFromState();
01543 
01544     // Leave the load menu
01545     _state->setViewType(kMenu);
01546     _state->setLocationNextAge(_state->getMenuSavedAge());
01547     _state->setLocationNextRoom(_state->getMenuSavedRoom());
01548     _state->setLocationNextNode(_state->getMenuSavedNode());
01549     _state->setMenuSavedAge(0);
01550     _state->setMenuSavedRoom(0);
01551     _state->setMenuSavedNode(0);
01552     _sound->resetSoundVars();
01553     _sound->stopMusic(15);
01554     _state->setSoundScriptsSuspended(0);
01555     _sound->playEffect(696, 60);
01556 
01557     goToNode(0, transition);
01558 
01559     return Common::kNoError;
01560 }
01561 
01562 static bool isValidSaveFileChar(char c) {
01563     // Limit it to letters, digits, and a few other characters that should be safe
01564     return Common::isAlnum(c) || c == ' ' || c == '_' || c == '+' || c == '-' || c == '.';
01565 }
01566 
01567 static bool isValidSaveFileName(const Common::String &desc) {
01568     for (uint32 i = 0; i < desc.size(); i++)
01569         if (!isValidSaveFileChar(desc[i]))
01570             return false;
01571 
01572     return true;
01573 }
01574 
01575 Common::Error Myst3Engine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
01576     assert(!desc.empty());
01577 
01578     if (!isValidSaveFileName(desc)) {
01579         return Common::Error(Common::kCreatingFileFailed, _("Invalid file name for saving"));
01580     }
01581 
01582     // Try to use an already generated thumbnail
01583     const Graphics::Surface *thumbnail = _menu->borrowSaveThumbnail();
01584     if (!thumbnail) {
01585         _menu->generateSaveThumbnail();
01586     }
01587     thumbnail = _menu->borrowSaveThumbnail();
01588     assert(thumbnail);
01589 
01590     return saveGameState(desc, thumbnail, isAutosave);
01591 }
01592 
01593 Common::Error Myst3Engine::saveGameState(const Common::String &desc, const Graphics::Surface *thumbnail, bool isAutosave) {
01594     // Strip extension
01595     Common::String saveName = desc;
01596     if (desc.hasSuffixIgnoreCase(".M3S") || desc.hasSuffixIgnoreCase(".M3X")) {
01597         saveName.erase(desc.size() - 4, desc.size());
01598     }
01599 
01600     Common::String fileName = Saves::buildName(saveName.c_str(), getPlatform());
01601 
01602     // Save the state and the thumbnail
01603     Common::SharedPtr<Common::OutSaveFile> save = Common::SharedPtr<Common::OutSaveFile>(_saveFileMan->openForSaving(fileName));
01604     if (!save) {
01605         return Common::kCreatingFileFailed;
01606     }
01607 
01608     Common::Error saveError = _state->save(save.get(), saveName, thumbnail, isAutosave);
01609     if (saveError.getCode() != Common::kNoError) {
01610         return saveError;
01611     }
01612 
01613     if (save->err()) {
01614         warning("An error occured when writing '%s'", fileName.c_str());
01615         return Common::kWritingFailed;
01616     }
01617 
01618     return saveError;
01619 }
01620 
01621 void Myst3Engine::animateDirectionChange(float targetPitch, float targetHeading, uint16 scriptTicks) {
01622     float startPitch = _state->getLookAtPitch();
01623     float startHeading = _state->getLookAtHeading();
01624 
01625     if (startPitch == targetPitch && startHeading == targetHeading)
01626         return; // Fast path
01627 
01628     float pitchDistance = targetPitch - startPitch;
01629     float headingDistance = targetHeading - startHeading;
01630 
01631     // Make sure to use the shortest direction
01632     while (ABS(headingDistance) > 180) {
01633         if (headingDistance > 0) {
01634             headingDistance -= 360;
01635         } else {
01636             headingDistance += 360;
01637         }
01638     }
01639 
01640     // Compute animation duration in frames
01641     float numTicks;
01642     if (scriptTicks) {
01643         numTicks = scriptTicks;
01644     } else {
01645         numTicks = sqrt(pitchDistance * pitchDistance + headingDistance * headingDistance)
01646                 * 30.0f / _state->getCameraMoveSpeed();
01647 
01648         if (numTicks > 0.0f)
01649             numTicks += 10.0f;
01650     }
01651 
01652     uint startTick = _state->getTickCount();
01653 
01654     // Draw animation
01655     if (numTicks != 0.0f) {
01656         while (1) {
01657             uint elapsedTicks = _state->getTickCount() - startTick;
01658             if (elapsedTicks >= numTicks || shouldQuit())
01659                 break;
01660 
01661             float step;
01662             if (numTicks >= 15) {
01663                 // Fast then slow movement
01664                 if (elapsedTicks > numTicks / 2.0f)
01665                     step = 1.0f - (numTicks - elapsedTicks) * (numTicks - elapsedTicks)
01666                                 / (numTicks / 2.0f * numTicks / 2.0f) / 2.0f;
01667                 else
01668                     step = elapsedTicks * elapsedTicks / (numTicks / 2.0f * numTicks / 2.0f) / 2.0f;
01669 
01670             } else {
01671                 // Constant speed movement
01672                 step = elapsedTicks / numTicks;
01673             }
01674 
01675             float nextPitch = startPitch + pitchDistance * step;
01676             float nextHeading = startHeading + headingDistance * step;
01677 
01678             _state->lookAt(nextPitch, nextHeading);
01679             drawFrame();
01680         }
01681     }
01682 
01683     _state->lookAt(targetPitch, targetHeading);
01684     drawFrame();
01685 }
01686 
01687 void Myst3Engine::getMovieLookAt(uint16 id, bool start, float &pitch, float &heading) {
01688     ResourceDescription desc = getFileDescription("", id, 0, Archive::kMovie);
01689 
01690     if (!desc.isValid())
01691         desc = getFileDescription("", id, 0, Archive::kMultitrackMovie);
01692 
01693     if (!desc.isValid())
01694         error("Movie %d does not exist", id);
01695 
01696     Math::Vector3d v;
01697     if (start)
01698         v = desc.getVideoData().v1;
01699     else
01700         v = desc.getVideoData().v2;
01701 
01702     Math::Vector2d horizontalProjection(v.x(), v.z());
01703     horizontalProjection.normalize();
01704 
01705     pitch = 90 - Math::Angle::arcCosine(v.y()).getDegrees();
01706     heading = Math::Angle::arcCosine(horizontalProjection.getY()).getDegrees();
01707 
01708     if (horizontalProjection.getX() < 0.0) {
01709         heading = 360 - heading;
01710     }
01711 }
01712 
01713 void Myst3Engine::playMovieGoToNode(uint16 movie, uint16 node) {
01714     uint16 room = _state->getLocationNextRoom();
01715     uint16 age = _state->getLocationNextAge();
01716 
01717     if (_state->getLocationNextNode()) {
01718         node = _state->getLocationNextNode();
01719     }
01720 
01721     if (_state->getViewType() == kCube && !_state->getCameraSkipAnimation()) {
01722         float startPitch, startHeading;
01723         getMovieLookAt(movie, true, startPitch, startHeading);
01724         animateDirectionChange(startPitch, startHeading, 0);
01725     }
01726     _state->setCameraSkipAnimation(0);
01727 
01728     loadNode(node, room, age);
01729 
01730     playSimpleMovie(movie, true, true);
01731 
01732     _state->setLocationNextNode(0);
01733     _state->setLocationNextRoom(0);
01734     _state->setLocationNextAge(0);
01735 
01736     if (_state->getViewType() == kCube) {
01737         float endPitch, endHeading;
01738         getMovieLookAt(movie, false, endPitch, endHeading);
01739         _state->lookAt(endPitch, endHeading);
01740     }
01741 
01742     setupTransition();
01743 }
01744 
01745 void Myst3Engine::playMovieFullFrame(uint16 movie) {
01746     if (_state->getViewType() == kCube && !_state->getCameraSkipAnimation()) {
01747         float startPitch, startHeading;
01748         getMovieLookAt(movie, true, startPitch, startHeading);
01749         animateDirectionChange(startPitch, startHeading, 0);
01750     }
01751     _state->setCameraSkipAnimation(0);
01752 
01753     playSimpleMovie(movie, true, false);
01754 
01755     if (_state->getViewType() == kCube) {
01756         float endPitch, endHeading;
01757         getMovieLookAt(movie, false, endPitch, endHeading);
01758         _state->lookAt(endPitch, endHeading);
01759     }
01760 
01761     setupTransition();
01762 }
01763 
01764 bool Myst3Engine::inputValidatePressed() {
01765     return _inputEnterPressed ||
01766             _inputSpacePressed ||
01767             getEventManager()->getButtonState() & Common::EventManager::LBUTTON;
01768 }
01769 
01770 bool Myst3Engine::inputEscapePressed() {
01771     return _inputEscapePressed;
01772 }
01773 
01774 bool Myst3Engine::inputSpacePressed() {
01775     return _inputSpacePressed;
01776 }
01777 
01778 bool Myst3Engine::inputTilePressed() {
01779     return _inputTildePressed;
01780 }
01781 
01782 void Myst3Engine::addSunSpot(uint16 pitch, uint16 heading, uint16 intensity,
01783         uint16 color, uint16 var, bool varControlledIntensity, uint16 radius) {
01784 
01785     SunSpot *s = new SunSpot();
01786 
01787     s->pitch = pitch;
01788     s->heading = heading;
01789     s->intensity = intensity * 2.55;
01790     s->color = (color & 0xF) | 16
01791             * ((color & 0xF) | 16
01792             * (((color >> 4) & 0xF) | 16
01793             * (((color >> 4) & 0xF) | 16
01794             * (((color >> 8) & 0xF) | 16
01795             * (((color >> 8) & 0xF))))));
01796     s->var = var;
01797     s->variableIntensity = varControlledIntensity;
01798     s->radius = radius;
01799 
01800     _sunspots.push_back(s);
01801 }
01802 
01803 SunSpot Myst3Engine::computeSunspotsIntensity(float pitch, float heading) {
01804     SunSpot result;
01805     result.intensity = -1;
01806     result.color = 0;
01807     result.radius = 0;
01808 
01809     for (uint i = 0; i < _sunspots.size(); i++) {
01810         SunSpot *s = _sunspots[i];
01811 
01812         uint32 value = _state->getVar(s->var);
01813 
01814         // Skip disabled items
01815         if (value == 0) continue;
01816 
01817         float distance = _scene->distanceToZone(s->heading, s->pitch, s->radius, heading, pitch);
01818 
01819         if (distance > result.radius) {
01820             result.radius = distance;
01821             result.color = s->color;
01822             result.intensity = s->intensity;
01823             result.variableIntensity = s->variableIntensity;
01824 
01825             if (result.variableIntensity) {
01826                 result.radius = value * distance / 100;
01827             }
01828         }
01829     }
01830 
01831     return result;
01832 }
01833 
01834 void Myst3Engine::settingsInitDefaults() {
01835     int defaultLanguage = _db->getGameLanguageCode();
01836 
01837     int defaultTextLanguage;
01838     if (getGameLocalizationType() == kLocMulti6)
01839         defaultTextLanguage = defaultLanguage;
01840     else
01841         defaultTextLanguage = getGameLanguage() != Common::EN_ANY;
01842 
01843     ConfMan.registerDefault("overall_volume", Audio::Mixer::kMaxMixerVolume);
01844     ConfMan.registerDefault("music_volume", Audio::Mixer::kMaxMixerVolume / 2);
01845     ConfMan.registerDefault("music_frequency", 75);
01846     ConfMan.registerDefault("audio_language", defaultLanguage);
01847     ConfMan.registerDefault("text_language", defaultTextLanguage);
01848     ConfMan.registerDefault("water_effects", true);
01849     ConfMan.registerDefault("transition_speed", 50);
01850     ConfMan.registerDefault("mouse_speed", 50);
01851     ConfMan.registerDefault("mouse_inverted", false);
01852     ConfMan.registerDefault("zip_mode", false);
01853     ConfMan.registerDefault("subtitles", false);
01854     ConfMan.registerDefault("vibrations", true); // Xbox specific
01855 }
01856 
01857 void Myst3Engine::settingsLoadToVars() {
01858     _state->setWaterEffects(ConfMan.getBool("water_effects"));
01859     _state->setTransitionSpeed(ConfMan.getInt("transition_speed"));
01860     _state->setMouseSpeed(ConfMan.getInt("mouse_speed"));
01861     _state->setZipModeEnabled(ConfMan.getBool("zip_mode"));
01862     _state->setSubtitlesEnabled(ConfMan.getBool("subtitles"));
01863 
01864     if (getPlatform() != Common::kPlatformXbox) {
01865         _state->setOverallVolume(CLIP<uint>(ConfMan.getInt("overall_volume") * 100 / 256, 0, 100));
01866         _state->setMusicVolume(CLIP<uint>(ConfMan.getInt("music_volume") * 100 / 256, 0, 100));
01867         _state->setMusicFrequency(ConfMan.getInt("music_frequency"));
01868         _state->setLanguageAudio(ConfMan.getInt("audio_language"));
01869         _state->setLanguageText(ConfMan.getInt("text_language"));
01870     } else {
01871         _state->setVibrationEnabled(ConfMan.getBool("vibrations"));
01872     }
01873 }
01874 
01875 void Myst3Engine::settingsApplyFromVars() {
01876     int32 oldTextLanguage = ConfMan.getInt("text_language");
01877 
01878     ConfMan.setInt("transition_speed", _state->getTransitionSpeed());
01879     ConfMan.setInt("mouse_speed", _state->getMouseSpeed());
01880     ConfMan.setBool("zip_mode", _state->getZipModeEnabled());
01881     ConfMan.setBool("subtitles", _state->getSubtitlesEnabled());
01882 
01883     if (getPlatform() != Common::kPlatformXbox) {
01884         ConfMan.setInt("overall_volume", _state->getOverallVolume() * 256 / 100);
01885         ConfMan.setInt("music_volume", _state->getMusicVolume() * 256 / 100);
01886         ConfMan.setInt("music_frequency", _state->getMusicFrequency());
01887         ConfMan.setInt("audio_language", _state->getLanguageAudio());
01888         ConfMan.setInt("text_language", _state->getLanguageText());
01889         ConfMan.setBool("water_effects", _state->getWaterEffects());
01890 
01891         // The language changed, reload the correct archives
01892         if (_state->getLanguageText() != oldTextLanguage) {
01893             closeArchives();
01894             openArchives();
01895         }
01896     } else {
01897         ConfMan.setBool("vibrations", _state->getVibrationEnabled());
01898     }
01899 
01900     // Mouse speed may have changed, refresh it
01901     _scene->updateMouseSpeed();
01902 
01903     syncSoundSettings();
01904 }
01905 
01906 void Myst3Engine::syncSoundSettings() {
01907     Engine::syncSoundSettings();
01908 
01909     uint soundOverall = ConfMan.getInt("overall_volume");
01910     uint soundVolumeMusic = ConfMan.getInt("music_volume");
01911 
01912     _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, soundOverall);
01913     _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic * soundOverall / 256);
01914 }
01915 
01916 bool Myst3Engine::isWideScreenModEnabled() const {
01917     return ConfMan.getBool("widescreen_mod");
01918 }
01919 
01920 void Myst3Engine::pauseEngineIntern(bool pause) {
01921     Engine::pauseEngineIntern(pause);
01922 
01923     if (!_state || !_cursor) {
01924         // This method may be called before the engine is fully initialized
01925         return;
01926     }
01927 
01928     for (uint i = 0; i < _movies.size(); i++) {
01929         _movies[i]->pause(pause);
01930     }
01931 
01932     _state->pauseEngine(pause);
01933 
01934     // Grab a game screen thumbnail in case we need one when writing a save file
01935     if (pause && !_menu->isOpen()) {
01936         // Opening the menu generates a save thumbnail so we only generate it if the menu is not open
01937         _menu->generateSaveThumbnail();
01938     }
01939 
01940     // Unlock the mouse so that the cursor is visible when the GMM opens
01941     if (_state->getViewType() == kCube && _cursor->isPositionLocked()) {
01942         _system->lockMouse(!pause);
01943     }
01944 
01945     // The user may have moved the mouse or resized the screen while the engine was paused
01946     if (!pause) {
01947         _gfx->computeScreenViewport();
01948         _cursor->updatePosition(_eventMan->getMousePos());
01949         _inventory->reflow();
01950     }
01951 }
01952 
01953 } // end of namespace Myst3


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