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

userinterface.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/events.h"
00024 #include "common/stream.h"
00025 #include "common/system.h"
00026 
00027 #include "engines/engine.h"
00028 
00029 #include "engines/stark/services/userinterface.h"
00030 
00031 #include "engines/stark/gfx/driver.h"
00032 
00033 #include "engines/stark/services/diary.h"
00034 #include "engines/stark/services/gameinterface.h"
00035 #include "engines/stark/services/global.h"
00036 #include "engines/stark/services/services.h"
00037 #include "engines/stark/services/staticprovider.h"
00038 #include "engines/stark/services/resourceprovider.h"
00039 #include "engines/stark/services/settings.h"
00040 
00041 #include "engines/stark/ui/cursor.h"
00042 #include "engines/stark/ui/dialogbox.h"
00043 #include "engines/stark/ui/menu/diaryindex.h"
00044 #include "engines/stark/ui/menu/mainmenu.h"
00045 #include "engines/stark/ui/menu/settingsmenu.h"
00046 #include "engines/stark/ui/menu/saveloadmenu.h"
00047 #include "engines/stark/ui/menu/fmvmenu.h"
00048 #include "engines/stark/ui/menu/diarypages.h"
00049 #include "engines/stark/ui/menu/dialogmenu.h"
00050 #include "engines/stark/ui/world/inventorywindow.h"
00051 #include "engines/stark/ui/world/fmvscreen.h"
00052 #include "engines/stark/ui/world/gamescreen.h"
00053 #include "engines/stark/ui/world/gamewindow.h"
00054 #include "engines/stark/ui/world/dialogpanel.h"
00055 
00056 #include "engines/stark/resources/knowledgeset.h"
00057 #include "engines/stark/resources/item.h"
00058 
00059 #include "gui/message.h"
00060 
00061 namespace Stark {
00062 
00063 UserInterface::UserInterface(Gfx::Driver *gfx) :
00064         _gfx(gfx),
00065         _cursor(nullptr),
00066         _diaryIndexScreen(nullptr),
00067         _mainMenuScreen(nullptr),
00068         _settingsMenuScreen(nullptr),
00069         _saveMenuScreen(nullptr),
00070         _loadMenuScreen(nullptr),
00071         _fmvMenuScreen(nullptr),
00072         _diaryPagesScreen(nullptr),
00073         _dialogScreen(nullptr),
00074         _exitGame(false),
00075         _quitToMainMenu(false),
00076         _shouldToggleSubtitle(false),
00077         _shouldGoBackToPreviousScreen(false),
00078         _fmvScreen(nullptr),
00079         _gameScreen(nullptr),
00080         _interactive(true),
00081         _interactionAttemptDenied(false),
00082         _currentScreen(nullptr),
00083         _gameWindowThumbnail(nullptr),
00084         _modalDialog(nullptr) {
00085 }
00086 
00087 UserInterface::~UserInterface() {
00088     freeGameScreenThumbnail();
00089 
00090     delete _modalDialog;
00091     delete _gameScreen;
00092     delete _fmvScreen;
00093     delete _diaryIndexScreen;
00094     delete _cursor;
00095     delete _mainMenuScreen;
00096     delete _settingsMenuScreen;
00097     delete _saveMenuScreen;
00098     delete _loadMenuScreen;
00099     delete _fmvMenuScreen;
00100     delete _diaryPagesScreen;
00101     delete _dialogScreen;
00102 }
00103 
00104 void UserInterface::init() {
00105     _cursor = new Cursor(_gfx);
00106 
00107     _mainMenuScreen = new MainMenuScreen(_gfx, _cursor);
00108     _gameScreen = new GameScreen(_gfx, _cursor);
00109     _diaryIndexScreen = new DiaryIndexScreen(_gfx, _cursor);
00110     _settingsMenuScreen = new SettingsMenuScreen(_gfx, _cursor);
00111     _saveMenuScreen = new SaveMenuScreen(_gfx, _cursor);
00112     _loadMenuScreen = new LoadMenuScreen(_gfx, _cursor);
00113     _fmvMenuScreen = new FMVMenuScreen(_gfx, _cursor);
00114     _diaryPagesScreen = new DiaryPagesScreen(_gfx, _cursor);
00115     _dialogScreen = new DialogScreen(_gfx, _cursor);
00116     _fmvScreen = new FMVScreen(_gfx, _cursor);
00117     _modalDialog = new DialogBox(_gfx, _cursor);
00118 
00119     _prevScreenNameStack.push(Screen::kScreenMainMenu);
00120     _currentScreen = _fmvScreen;
00121 
00122     // Play the FunCom logo video
00123     _fmvScreen->play("1402.bbb");
00124 }
00125 
00126 void UserInterface::onGameLoop() {
00127     StarkStaticProvider->onGameLoop();
00128 
00129     if (_modalDialog->isVisible()) {
00130         _modalDialog->handleGameLoop();
00131         _modalDialog->handleMouseMove();
00132     } else {
00133         _currentScreen->handleGameLoop();
00134 
00135         // Check for UI mouse overs
00136         // TODO: Call mouse move only if the mouse position actually changed
00137         //  probably some code will need to be moved to gameloop handling to
00138         //  account for the case where hotspots move and the mouse cursor needs
00139         //  to be updated regardless of the mouse actually moved.
00140         _currentScreen->handleMouseMove();
00141     }
00142 }
00143 
00144 void UserInterface::handleMouseMove(const Common::Point &pos) {
00145     _cursor->setMousePosition(pos);
00146 }
00147 
00148 void UserInterface::handleMouseUp() {
00149     // Only the settings menu needs to handle this event
00150     // TODO: Clean this up
00151     _settingsMenuScreen->handleMouseUp();
00152 }
00153 
00154 void UserInterface::handleClick() {
00155     if (_modalDialog->isVisible()) {
00156         _modalDialog->handleClick();
00157     } else {
00158         _currentScreen->handleClick();
00159     }
00160 }
00161 
00162 void UserInterface::handleRightClick() {
00163     if (_modalDialog->isVisible()) {
00164         _modalDialog->handleRightClick();
00165     } else {
00166         _currentScreen->handleRightClick();
00167     }
00168 }
00169 
00170 void UserInterface::handleDoubleClick() {
00171     if (_modalDialog->isVisible()) {
00172         _modalDialog->handleDoubleClick();
00173     } else {
00174         _currentScreen->handleDoubleClick();
00175     }
00176 }
00177 
00178 void UserInterface::handleEscape() {
00179     if (StarkGameInterface->skipCurrentSpeeches()) {
00180         return;
00181     }
00182 
00183     if (skipFMV()) {
00184         return;
00185     }
00186 
00187     Screen::Name curScreenName = _currentScreen->getName();
00188     if (curScreenName != Screen::kScreenGame && curScreenName != Screen::kScreenMainMenu) {
00189         backPrevScreen();
00190     } else if (StarkSettings->getBoolSetting(Settings::kTimeSkip)) {
00191         StarkGlobal->setFastForward();
00192     }
00193 }
00194 
00195 void UserInterface::inventoryOpen(bool open) {
00196     // Make the inventory update its contents.
00197     if (open) {
00198         _gameScreen->getInventoryWindow()->open();
00199     } else {
00200         _gameScreen->getInventoryWindow()->close();
00201     }
00202 }
00203 
00204 int16 UserInterface::getSelectedInventoryItem() const {
00205     if (_gameScreen) {
00206         return _gameScreen->getInventoryWindow()->getSelectedInventoryItem();
00207     } else {
00208         return -1;
00209     }
00210 }
00211 
00212 void UserInterface::selectInventoryItem(int16 itemIndex) {
00213     _gameScreen->getInventoryWindow()->setSelectedInventoryItem(itemIndex);
00214 }
00215 
00216 void UserInterface::requestFMVPlayback(const Common::String &name) {
00217     _shouldPlayFmv = name;
00218 }
00219 
00220 void UserInterface::onFMVStopped() {
00221     _shouldGoBackToPreviousScreen = true;
00222 }
00223 
00224 void UserInterface::changeScreen(Screen::Name screenName) {
00225     if (screenName == _currentScreen->getName()) {
00226         return;
00227     }
00228 
00229     _prevScreenNameStack.push(_currentScreen->getName());
00230     _currentScreen->close();
00231     _currentScreen = getScreenByName(screenName);
00232     _currentScreen->open();
00233 }
00234 
00235 void UserInterface::backPrevScreen() {
00236     // No need to check the stack since at least there will be a MainMenuScreen in it
00237     // and MainMenuScreen will not request to go back
00238     changeScreen(_prevScreenNameStack.pop());
00239 
00240     // No need to push for going back
00241     _prevScreenNameStack.pop();
00242 }
00243 
00244 void UserInterface::restoreScreenHistory() {
00245     _shouldGoBackToPreviousScreen = false;
00246     _prevScreenNameStack.clear();
00247     _prevScreenNameStack.push(Screen::kScreenMainMenu);
00248 }
00249 
00250 Screen *UserInterface::getScreenByName(Screen::Name screenName) const {
00251     switch (screenName) {
00252         case Screen::kScreenFMV:
00253             return _fmvScreen;
00254         case Screen::kScreenDiaryIndex:
00255             return _diaryIndexScreen;
00256         case Screen::kScreenGame:
00257             return _gameScreen;
00258         case Screen::kScreenMainMenu:
00259             return _mainMenuScreen;
00260         case Screen::kScreenSettingsMenu:
00261             return _settingsMenuScreen;
00262         case Screen::kScreenSaveMenu:
00263             return _saveMenuScreen;
00264         case Screen::kScreenLoadMenu:
00265             return _loadMenuScreen;
00266         case Screen::kScreenFMVMenu:
00267             return _fmvMenuScreen;
00268         case Screen::kScreenDiaryPages:
00269             return _diaryPagesScreen;
00270         case Screen::kScreenDialog:
00271             return _dialogScreen;
00272         default:
00273             error("Unhandled screen name '%d'", screenName);
00274     }
00275 }
00276 
00277 bool UserInterface::isInGameScreen() const {
00278     return _currentScreen && (_currentScreen->getName() == Screen::kScreenGame);
00279 }
00280 
00281 bool UserInterface::isInSaveLoadMenuScreen() const {
00282     Screen::Name name = _currentScreen->getName();
00283     return name == Screen::kScreenSaveMenu || name == Screen::kScreenLoadMenu;
00284 }
00285 
00286 bool UserInterface::isInDiaryIndexScreen() const {
00287     return _currentScreen->getName() == Screen::kScreenDiaryIndex;
00288 }
00289 
00290 bool UserInterface::isInventoryOpen() const {
00291     return _gameScreen->getInventoryWindow()->isVisible();
00292 }
00293 
00294 bool UserInterface::skipFMV() {
00295     if (_currentScreen->getName() == Screen::kScreenFMV) {
00296         _fmvScreen->stop();
00297         return true;
00298     }
00299     return false;
00300 }
00301 
00302 void UserInterface::render() {
00303     _currentScreen->render();
00304 
00305     if (_modalDialog->isVisible()) {
00306         _modalDialog->render();
00307     }
00308 
00309     // The cursor depends on the UI being done.
00310     if (_currentScreen->getName() != Screen::kScreenFMV) {
00311         _cursor->render();
00312     }
00313 }
00314 
00315 bool UserInterface::isInteractive() const {
00316     return _interactive;
00317 }
00318 
00319 void UserInterface::setInteractive(bool interactive) {
00320     if (interactive && !_interactive) {
00321         StarkGlobal->setNormalSpeed();
00322     } else if (!interactive && _interactive) {
00323         _interactionAttemptDenied = false;
00324     }
00325 
00326     _interactive = interactive;
00327 }
00328 
00329 void UserInterface::markInteractionDenied() {
00330     if (!_interactive) {
00331         _interactionAttemptDenied = true;
00332     }
00333 }
00334 
00335 bool UserInterface::wasInteractionDenied() const {
00336     return !_interactive && _interactionAttemptDenied;
00337 }
00338 
00339 void UserInterface::clearLocationDependentState() {
00340     _gameScreen->reset();
00341 }
00342 
00343 void UserInterface::optionsOpen() {
00344     changeScreen(Screen::kScreenDiaryIndex);
00345 }
00346 
00347 void UserInterface::saveGameScreenThumbnail() {
00348     freeGameScreenThumbnail();
00349 
00350     if (StarkGlobal->getLevel() && StarkGlobal->getCurrent()) {
00351         // Re-render the screen to exclude the cursor
00352         StarkGfx->clearScreen();
00353         _gameScreen->render();
00354     }
00355 
00356     Graphics::Surface *big = _gameScreen->getGameWindow()->getScreenshot();
00357     assert(big->format.bytesPerPixel == 4);
00358 
00359     _gameWindowThumbnail = new Graphics::Surface();
00360     _gameWindowThumbnail->create(kThumbnailWidth, kThumbnailHeight, big->format);
00361 
00362     uint32 *dst = (uint32 *)_gameWindowThumbnail->getPixels();
00363     for (uint i = 0; i < _gameWindowThumbnail->h; i++) {
00364         for (uint j = 0; j < _gameWindowThumbnail->w; j++) {
00365             uint32 srcX = big->w * j / _gameWindowThumbnail->w;
00366             uint32 srcY = big->h * i / _gameWindowThumbnail->h;
00367             uint32 *src = (uint32 *)big->getBasePtr(srcX, srcY);
00368 
00369             // Copy RGBA pixel
00370             *dst++ = *src;
00371         }
00372     }
00373 
00374     big->free();
00375     delete big;
00376 }
00377 
00378 void UserInterface::freeGameScreenThumbnail() {
00379     if (_gameWindowThumbnail) {
00380         _gameWindowThumbnail->free();
00381         delete _gameWindowThumbnail;
00382         _gameWindowThumbnail = nullptr;
00383     }
00384 }
00385 
00386 const Graphics::Surface *UserInterface::getGameWindowThumbnail() const {
00387     return _gameWindowThumbnail;
00388 }
00389 
00390 void UserInterface::onScreenChanged() {
00391     _gameScreen->onScreenChanged();
00392 
00393     if (_modalDialog->isVisible()) {
00394         _modalDialog->onScreenChanged();
00395     }
00396 
00397     if (!isInGameScreen()) {
00398         _currentScreen->onScreenChanged();
00399     }
00400 }
00401 
00402 void UserInterface::notifyInventoryItemEnabled(uint16 itemIndex) {
00403     _gameScreen->notifyInventoryItemEnabled(itemIndex);
00404 }
00405 
00406 void UserInterface::notifyDiaryEntryEnabled() {
00407     _gameScreen->notifyDiaryEntryEnabled();
00408 }
00409 
00410 void UserInterface::toggleScreen(Screen::Name screenName) {
00411     Screen::Name currentName = _currentScreen->getName();
00412 
00413     if (currentName == screenName
00414             || (currentName == Screen::kScreenSaveMenu && screenName == Screen::kScreenLoadMenu)
00415             || (currentName == Screen::kScreenLoadMenu && screenName == Screen::kScreenSaveMenu)) {
00416         backPrevScreen();
00417     } else if (currentName == Screen::kScreenGame 
00418             || currentName == Screen::kScreenDiaryIndex
00419             || (currentName == Screen::kScreenMainMenu && screenName == Screen::kScreenLoadMenu)
00420             || (currentName == Screen::kScreenMainMenu && screenName == Screen::kScreenSettingsMenu)) {
00421         changeScreen(screenName);
00422     }
00423 }
00424 
00425 void UserInterface::performToggleSubtitle() {
00426     StarkSettings->flipSetting(Settings::kSubtitle);
00427     _shouldToggleSubtitle = false;
00428 }
00429 
00430 void UserInterface::cycleInventory(bool forward) {
00431     int16 curItem = getSelectedInventoryItem();
00432     int16 nextItem = StarkGlobal->getInventory()->getNeighborInventoryItem(curItem, forward);
00433     selectInventoryItem(nextItem);
00434 }
00435 
00436 void UserInterface::doQueuedScreenChange() {
00437     if (_quitToMainMenu) {
00438         clearLocationDependentState();
00439         changeScreen(Screen::kScreenGame);
00440         StarkResourceProvider->shutdown();
00441         changeScreen(Screen::kScreenMainMenu);
00442         _prevScreenNameStack.clear();
00443         _quitToMainMenu = false;
00444     }
00445 
00446     if (_shouldGoBackToPreviousScreen) {
00447         backPrevScreen();
00448         _shouldGoBackToPreviousScreen = false;
00449     }
00450 
00451     if (!_shouldPlayFmv.empty()) {
00452         changeScreen(Screen::kScreenFMV);
00453         _fmvScreen->play(_shouldPlayFmv);
00454         _shouldPlayFmv.clear();
00455     }
00456 }
00457 
00458 void UserInterface::handleKeyPress(const Common::KeyState &keyState) {
00459     if (_modalDialog->isVisible()) {
00460         _modalDialog->onKeyPress(keyState);
00461         return;
00462     }
00463 
00464     // TODO: Delegate keypress handling to the screens
00465 
00466     if (keyState.keycode == Common::KEYCODE_ESCAPE) {
00467         handleEscape();
00468     } else if ((keyState.keycode == Common::KEYCODE_RETURN
00469                 || keyState.keycode == Common::KEYCODE_KP_ENTER)) {
00470         if (isInGameScreen()) {
00471             _gameScreen->getDialogPanel()->selectFocusedOption();
00472         }
00473     } else if (keyState.keycode == Common::KEYCODE_F1) {
00474         toggleScreen(Screen::kScreenDiaryIndex);
00475     } else if (keyState.keycode == Common::KEYCODE_F2) {
00476         if (isInSaveLoadMenuScreen() || g_engine->canSaveGameStateCurrently()) {
00477             toggleScreen(Screen::kScreenSaveMenu);
00478         }
00479     } else if (keyState.keycode == Common::KEYCODE_F3) {
00480         toggleScreen(Screen::kScreenLoadMenu);
00481     } else if (keyState.keycode == Common::KEYCODE_F4) {
00482         toggleScreen(Screen::kScreenDialog);
00483     } else if (keyState.keycode == Common::KEYCODE_F5) {
00484         if (StarkDiary->isEnabled()) {
00485             toggleScreen(Screen::kScreenDiaryPages);
00486         }
00487     } else if (keyState.keycode == Common::KEYCODE_F6) {
00488         toggleScreen(Screen::kScreenFMVMenu);
00489     } else if (keyState.keycode == Common::KEYCODE_F7) {
00490         toggleScreen(Screen::kScreenSettingsMenu);
00491     } else if (keyState.keycode == Common::KEYCODE_F8) {
00492         g_system->saveScreenshot();
00493     } else if (keyState.keycode == Common::KEYCODE_F9) {
00494         if (isInGameScreen()) {
00495             _shouldToggleSubtitle = !_shouldToggleSubtitle;
00496         }
00497     } else if (keyState.keycode == Common::KEYCODE_F10) {
00498         if (isInGameScreen() || isInDiaryIndexScreen()) {
00499             confirm(GameMessage::kQuitGamePrompt, this, &UserInterface::requestQuitToMainMenu);
00500         }
00501     } else if (keyState.keycode == Common::KEYCODE_a) {
00502         if (isInGameScreen() && isInteractive()) {
00503             cycleInventory(false);
00504         }
00505     } else if (keyState.keycode == Common::KEYCODE_s) {
00506         if (isInGameScreen() && isInteractive()) {
00507             cycleInventory(true);
00508         }
00509     } else if (keyState.keycode == Common::KEYCODE_i) {
00510         if (isInGameScreen() && isInteractive()) {
00511             inventoryOpen(!isInventoryOpen());
00512         }
00513     } else if (keyState.keycode == Common::KEYCODE_x
00514                 && !keyState.hasFlags(Common::KBD_ALT)) {
00515         if (isInGameScreen() && isInteractive()) {
00516             _gameScreen->getGameWindow()->toggleExitDisplay();
00517         }
00518     } else if ((keyState.keycode == Common::KEYCODE_x
00519                 || keyState.keycode == Common::KEYCODE_q)
00520                 && keyState.hasFlags(Common::KBD_ALT)) {
00521         confirm(GameMessage::kQuitPrompt, this, &UserInterface::notifyShouldExit);
00522     } else if (keyState.keycode == Common::KEYCODE_p) {
00523         if (isInGameScreen()) {
00524             g_engine->pauseEngine(true);
00525             debug("The game is paused");
00526         }
00527     } else if (keyState.keycode == Common::KEYCODE_PAGEUP) {
00528         if (isInGameScreen()) {
00529             if (isInventoryOpen()) {
00530                 _gameScreen->getInventoryWindow()->scrollUp();
00531             } else {
00532                 _gameScreen->getDialogPanel()->scrollUp();
00533             }
00534         }
00535     } else if (keyState.keycode == Common::KEYCODE_UP) {
00536         if (isInGameScreen()) {
00537             if (isInventoryOpen()) {
00538                 _gameScreen->getInventoryWindow()->scrollUp();
00539             } else {
00540                 _gameScreen->getDialogPanel()->focusPrevOption();
00541             }
00542         }
00543     } else if (keyState.keycode == Common::KEYCODE_PAGEDOWN) {
00544         if (isInGameScreen()) {
00545             if (isInventoryOpen()) {
00546                 _gameScreen->getInventoryWindow()->scrollDown();
00547             } else {
00548                 _gameScreen->getDialogPanel()->scrollDown();
00549             }
00550         }
00551     } else if (keyState.keycode == Common::KEYCODE_DOWN) {
00552         if (isInGameScreen()) {
00553             if (isInventoryOpen()) {
00554                 _gameScreen->getInventoryWindow()->scrollDown();
00555             } else {
00556                 _gameScreen->getDialogPanel()->focusNextOption();
00557             }
00558         }
00559     } else if (keyState.keycode >= Common::KEYCODE_1 && keyState.keycode <= Common::KEYCODE_9) {
00560         if (isInGameScreen()) {
00561             uint index = keyState.keycode - Common::KEYCODE_1;
00562             _gameScreen->getDialogPanel()->selectOption(index);
00563         }
00564     }
00565 }
00566 
00567 void UserInterface::confirm(const Common::String &message, Common::Functor0<void> *confirmCallBack) {
00568     Common::String textYes = StarkGameMessage->getTextByKey(GameMessage::kYes);
00569     Common::String textNo = StarkGameMessage->getTextByKey(GameMessage::kNo);
00570 
00571     _modalDialog->open(message, confirmCallBack, textYes, textNo);
00572 }
00573 
00574 void UserInterface::confirm(GameMessage::TextKey key, Common::Functor0<void> *confirmCallBack) {
00575     Common::String message = StarkGameMessage->getTextByKey(key);
00576 
00577     confirm(message, confirmCallBack);
00578 }
00579 
00580 } // End of namespace Stark


Generated on Sat Mar 16 2019 05:02:00 for ResidualVM by doxygen 1.7.1
curved edge   curved edge