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


Generated on Sat Jul 20 2019 05:00:55 for ResidualVM by doxygen 1.7.1
curved edge   curved edge