diff --git a/README.md b/README.md index 84eba7fd2..1db15f9ca 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,11 @@ following resources to learn the basic gameplay concepts: You can play singleplayer levels using the Skirmish menu, or host/join a multiplayer game by using the corresponding menus. +Use Load Game in the game options, or F8 while hosting a local game, to open +the saved-game browser; Back preserves the current game until another save is +selected. Loaded single-player saves resume their stored side without a new +seat-selection step. Long save descriptions can be scrolled. + ### Be part of the community As free software aficionados, we value community-based development and diff --git a/gui/MenuLoad.layout b/gui/MenuLoad.layout index 94d383fcc..b08035fe6 100644 --- a/gui/MenuLoad.layout +++ b/gui/MenuLoad.layout @@ -27,18 +27,20 @@ - + - - + + - + - + + + diff --git a/gui/OD.looknfeel b/gui/OD.looknfeel index 15c7c8d4a..608351d2a 100644 --- a/gui/OD.looknfeel +++ b/gui/OD.looknfeel @@ -4772,4 +4772,73 @@ + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/ODSkin.scheme b/gui/ODSkin.scheme index f3fc54304..801554cb8 100644 --- a/gui/ODSkin.scheme +++ b/gui/ODSkin.scheme @@ -57,4 +57,6 @@ + + diff --git a/gui/WindowGameOptions.layout b/gui/WindowGameOptions.layout index 879699faa..cb3087722 100644 --- a/gui/WindowGameOptions.layout +++ b/gui/WindowGameOptions.layout @@ -25,8 +25,7 @@ - - + diff --git a/source/modes/AbstractApplicationMode.cpp b/source/modes/AbstractApplicationMode.cpp index e93a4e54f..ef4aee4c9 100755 --- a/source/modes/AbstractApplicationMode.cpp +++ b/source/modes/AbstractApplicationMode.cpp @@ -24,6 +24,29 @@ #include #include #include +#include +#include +#include + +#include + +namespace +{ +void collectEscapeWindows(CEGUI::Window* window, std::vector& windows) +{ + if(window == nullptr || !window->isVisible() || window->isDisabled()) + return; + + CEGUI::Combobox* combo = dynamic_cast(window); + if(dynamic_cast(window) != nullptr || + dynamic_cast(window) != nullptr || + (combo != nullptr && combo->isDropDownListVisible())) + windows.push_back(window); + + for(size_t i = 0; i < window->getChildCount(); ++i) + collectEscapeWindows(window->getChildAtIdx(i), windows); +} +} AbstractApplicationMode::~AbstractApplicationMode() { @@ -78,6 +101,11 @@ bool AbstractApplicationMode::keyPressed(const OIS::KeyEvent& arg) { switch (arg.key) { + case OIS::KC_ESCAPE: + if(!CEGUI::System::getSingleton().getDefaultGUIContext().injectKeyDown(CEGUI::Key::Escape) && + !closeTopWindow() && mModeType != ModeManager::ADVERTISMENT) + goBack(); + break; default: CEGUI::System::getSingleton().getDefaultGUIContext().injectChar( static_cast(arg.text)); @@ -104,3 +132,34 @@ bool AbstractApplicationMode::goBack(const CEGUI::EventArgs&) mModeManager->requestPreviousMode(); return true; } + +bool AbstractApplicationMode::closeTopWindow() +{ + CEGUI::GUIContext& context = CEGUI::System::getSingleton().getDefaultGUIContext(); + std::vector windows; + collectEscapeWindows(context.getModalWindow() != nullptr ? + context.getModalWindow() : context.getRootWindow(), windows); + std::sort(windows.begin(), windows.end(), [](CEGUI::Window* left, CEGUI::Window* right) + { + return left->isInFront(*right); + }); + + for(CEGUI::Window* window : windows) + { + if(CEGUI::Combobox* combo = dynamic_cast(window)) + { + combo->hideDropList(); + return true; + } + if(CEGUI::PopupMenu* popup = dynamic_cast(window)) + { + popup->closePopupMenu(); + return true; + } + CEGUI::WindowEventArgs args(window); + window->fireEvent(CEGUI::FrameWindow::EventCloseClicked, args); + if(args.handled != 0) + return true; + } + return false; +} diff --git a/source/modes/AbstractApplicationMode.h b/source/modes/AbstractApplicationMode.h index ffee186cb..30fda6e62 100755 --- a/source/modes/AbstractApplicationMode.h +++ b/source/modes/AbstractApplicationMode.h @@ -126,6 +126,9 @@ class AbstractApplicationMode : {} protected: + //! Close the frontmost visible GUI window through its existing cancel handler. + bool closeTopWindow(); + ModeManager& getModeManager() { return *mModeManager; diff --git a/source/modes/EditorMode.cpp b/source/modes/EditorMode.cpp index bafb65c33..ee5f19fd4 100755 --- a/source/modes/EditorMode.cpp +++ b/source/modes/EditorMode.cpp @@ -340,11 +340,6 @@ EditorMode::EditorMode(ModeManager* modeManager): CEGUI::FrameWindow::EventCloseClicked, CEGUI::Event::Subscriber(&EditorMode::toggleOptionsWindow, this) )); - addEventConnection( - mRootWindow->getChild("EditorOptionsWindow")->subscribeEvent( - CEGUI::FrameWindow::EventCloseClicked, - CEGUI::Event::Subscriber(&EditorMode::toggleOptionsWindow, this) - )); addEventConnection( mRootWindow->getChild("EditorOptionsWindow/SaveLevelButton") ->subscribeEvent( @@ -1497,9 +1492,14 @@ bool EditorMode::hidePortalWaveWindow(const CEGUI::EventArgs& /*arg*/) bool EditorMode::keyPressed(const OIS::KeyEvent &arg) { // Inject key to the gui currently displayed - CEGUI::System::getSingleton().getDefaultGUIContext().injectKeyDown(static_cast(arg.key)); + const bool guiHandledKey = CEGUI::System::getSingleton().getDefaultGUIContext().injectKeyDown( + static_cast(arg.key)); CEGUI::System::getSingleton().getDefaultGUIContext().injectChar(arg.text); + if(arg.key == OIS::KC_ESCAPE && mCurrentInputMode != InputModeConsole && + mCurrentInputMode != InputModeChat && (guiHandledKey || closeTopWindow())) + return true; + if (mCurrentInputMode == InputModeChat || mCurrentInputMode == InputModeSave || mCurrentInputMode == InputModeLoad || mCurrentInputMode == InputModeNew) return true; diff --git a/source/modes/GameMode.cpp b/source/modes/GameMode.cpp index d364c92ca..5ea58dd61 100755 --- a/source/modes/GameMode.cpp +++ b/source/modes/GameMode.cpp @@ -30,6 +30,7 @@ #include "gamemap/Pathfinding.h" #include "modes/GameEditorModeConsole.h" #include "modes/InputBridge.h" +#include "modes/MenuModeLoad.h" #include "network/ChatEventMessage.h" #include "network/ODClient.h" #include "network/ODServer.h" @@ -210,6 +211,10 @@ GameMode::GameMode(ModeManager *modeManager): ) ); saveGameButtonWindow->setEnabled(ODServer::getSingleton().isConnected()); + CEGUI::Window* loadGameButtonWindow = guiSheet->getChild("GameOptionsWindow/LoadGameButton"); + addEventConnection(loadGameButtonWindow->subscribeEvent(CEGUI::PushButton::EventClicked, + CEGUI::Event::Subscriber(&GameMode::loadGame, this))); + loadGameButtonWindow->setEnabled(ODServer::getSingleton().isConnected()); addEventConnection( guiSheet->getChild("GameOptionsWindow/SettingsButton")->subscribeEvent( CEGUI::PushButton::EventClicked, @@ -792,8 +797,11 @@ bool GameMode::mouseReleased(const OIS::MouseEvent &arg, OIS::MouseButtonID id) bool GameMode::keyPressed(const OIS::KeyEvent& arg) { + if(mLoadMenu && mLoadMenu->isOpenInGame()) + return mLoadMenu->keyPressed(arg); // Inject key to Gui - CEGUI::System::getSingleton().getDefaultGUIContext().injectKeyDown(static_cast(arg.key)); + const bool guiHandledKey = CEGUI::System::getSingleton().getDefaultGUIContext().injectKeyDown( + static_cast(arg.key)); if (arg.text != 0 && !getConsole()->isFreshlyEnabled()) { CEGUI::System::getSingleton().getDefaultGUIContext().injectChar(arg.text); @@ -807,6 +815,8 @@ bool GameMode::keyPressed(const OIS::KeyEvent& arg) return getConsole()->keyPressed(arg); case InputModeNormal: default: + if(arg.key == OIS::KC_ESCAPE && guiHandledKey) + return true; return keyPressedNormal(arg); } } @@ -837,6 +847,10 @@ bool GameMode::keyPressedNormal(const OIS::KeyEvent &arg) saveGame(); break; + case OIS::KC_F8: + loadGame(); + break; + case OIS::KC_F9: RenderManager::getSingleton().rrToggleHandSelectorVisibility(); break; @@ -934,8 +948,15 @@ bool GameMode::keyPressedNormal(const OIS::KeyEvent &arg) break; } - // Quit the game + // Close one GUI layer before considering a new exit confirmation. case OIS::KC_ESCAPE: + if(closeTopWindow()) + break; + if(mRootWindow->getChild("GameEventText")->isVisible()) + { + mRootWindow->getChild("GameEventText")->hide(); + break; + } mExitToDesktop = false; popupExit(!mGameMap->getGamePaused()); break; @@ -1404,6 +1425,18 @@ bool GameMode::showSkillFromOptions(const CEGUI::EventArgs& /*e*/) return true; } +bool GameMode::loadGame(const CEGUI::EventArgs& /*e*/) +{ + if(!ODServer::getSingleton().isConnected()) + return true; + if(!mLoadMenu) + mLoadMenu.reset(new MenuModeLoad(&getModeManager(), true)); + // Retain the options window so returning from the browser restores its caller. + showOptionsWindow(); + mLoadMenu->activate(); + return true; +} + bool GameMode::saveGame(const CEGUI::EventArgs& /*e*/) { // We can save if launching in server mode only diff --git a/source/modes/GameMode.h b/source/modes/GameMode.h index 1451eb02d..b174aa958 100755 --- a/source/modes/GameMode.h +++ b/source/modes/GameMode.h @@ -33,6 +33,7 @@ class Window; } class Creature; +class MenuModeLoad; enum class SpellType; enum class SkillType; @@ -208,6 +209,7 @@ class GameMode final : public GameEditorModeBase, public InputCommand bool showObjectivesFromOptions(const CEGUI::EventArgs& e = {}); bool showSkillFromOptions(const CEGUI::EventArgs& e = {}); bool saveGame(const CEGUI::EventArgs& e = {}); + bool loadGame(const CEGUI::EventArgs& e = {}); bool showSettingsFromOptions(const CEGUI::EventArgs& e = {}); //! \brief Handle the keyboard input in normal mode @@ -220,6 +222,7 @@ class GameMode final : public GameEditorModeBase, public InputCommand virtual bool keyReleasedNormal (const OIS::KeyEvent &arg); private: + std::unique_ptr mLoadMenu; //! \brief Whether the pending exit confirmation should leave to the desktop //! rather than back to the main menu. Set by the button that opened the //! confirmation popup. diff --git a/source/modes/MenuModeLoad.cpp b/source/modes/MenuModeLoad.cpp index ccd3f0104..6dc714064 100755 --- a/source/modes/MenuModeLoad.cpp +++ b/source/modes/MenuModeLoad.cpp @@ -33,19 +33,21 @@ #include "utils/ResourceManager.h" #include +#include #include "boost/filesystem.hpp" const std::string SAVEGAME_EXTENSION = ".level"; -MenuModeLoad::MenuModeLoad(ModeManager *modeManager): - AbstractApplicationMode(modeManager, ModeManager::MENU_LOAD_SAVEDGAME) +MenuModeLoad::MenuModeLoad(ModeManager *modeManager, bool inGame, const std::string& savedGame): + AbstractApplicationMode(modeManager, ModeManager::MENU_LOAD_SAVEDGAME), + mInGame(inGame), + mSavedGame(savedGame) { CEGUI::Window* window = modeManager->getGui().getGuiSheet(Gui::guiSheet::loadSavedGameMenu); addEventConnection( window->getChild("LevelWindowFrame/BackButton")->subscribeEvent( CEGUI::PushButton::EventClicked, - CEGUI::Event::Subscriber(&AbstractApplicationMode::goBack, - static_cast(this)) + CEGUI::Event::Subscriber(&MenuModeLoad::closeBrowser, this) ) ); addEventConnection( @@ -75,26 +77,55 @@ MenuModeLoad::MenuModeLoad(ModeManager *modeManager): addEventConnection( window->getChild("LevelWindowFrame")->subscribeEvent( CEGUI::FrameWindow::EventCloseClicked, - CEGUI::Event::Subscriber(&AbstractApplicationMode::goBack, - static_cast(this)) + CEGUI::Event::Subscriber(&MenuModeLoad::closeBrowser, this) ) ); } +MenuModeLoad::~MenuModeLoad() +{ + if(isOpenInGame()) + closeBrowser(); +} + +bool MenuModeLoad::closeBrowser(const CEGUI::EventArgs&) +{ + if(!mInGame) + return goBack(); + mOpen = false; + ODFrameListener::getSingleton().getClientGameMap()->setGamePaused(mWasPaused); + getModeManager().getGui().loadGuiSheet(Gui::inGameMenu); + return true; +} + void MenuModeLoad::activate() { // Loads the corresponding Gui sheet. getModeManager().getGui().loadGuiSheet(Gui::guiSheet::loadSavedGameMenu); - giveFocus(); - - // Play the main menu music - MusicPlayer::getSingleton().play(ConfigManager::getSingleton().getMainMenuMusic()); - - GameMap* gameMap = ODFrameListener::getSingleton().getClientGameMap(); - gameMap->clearAll(); - gameMap->setGamePaused(true); + CEGUI::Window* sheet = getModeManager().getGui().getGuiSheet(Gui::loadSavedGameMenu); + sheet->getChild("WelcomeBanner")->setVisible(!mInGame); + sheet->getChild("VersionText")->setVisible(!mInGame); + if(mInGame) + { + if(!mOpen) + mWasPaused = gameMap->getGamePaused(); + mOpen = true; + gameMap->setGamePaused(true); + } + else + { + giveFocus(); + MusicPlayer::getSingleton().play(ConfigManager::getSingleton().getMainMenuMusic()); + gameMap->clearAll(); + gameMap->setGamePaused(true); + if(!mSavedGame.empty()) + { + ODFrameListener::getSingleton().stopGameRenderer(); + ODFrameListener::getSingleton().createMainMenuScene(); + } + } CEGUI::Window* tmpWin = getModeManager().getGui().getGuiSheet(Gui::loadSavedGameMenu)->getChild("LevelWindowFrame/SaveGameSelect"); CEGUI::Listbox* levelSelectList = static_cast(tmpWin); @@ -103,6 +134,7 @@ void MenuModeLoad::activate() tmpWin->hide(); mFilesList.clear(); levelSelectList->resetList(); + sheet->getChild("LevelWindowFrame/MapDescriptionText")->setText(""); std::string levelPath = ResourceManager::getSingleton().getSaveGamePath(); if(Helper::fillFilesList(levelPath, mFilesList, SAVEGAME_EXTENSION)) @@ -116,6 +148,13 @@ void MenuModeLoad::activate() levelSelectList->addItem(item); } } + if(!mSavedGame.empty()) + { + const std::string filename = mSavedGame; + mSavedGame.clear(); + launchSavedGame(filename); + } + } bool MenuModeLoad::launchSelectedButtonPressed(const CEGUI::EventArgs&) @@ -131,18 +170,27 @@ bool MenuModeLoad::launchSelectedButtonPressed(const CEGUI::EventArgs&) return true; } + const uint32_t id = levelSelectList->getFirstSelectedItem()->getID(); + if(id >= mFilesList.size()) + return true; + const std::string level = mFilesList[id]; + if(mInGame) + { + getModeManager().requestSavedGame(level); + return true; + } + return launchSavedGame(level); +} + +bool MenuModeLoad::launchSavedGame(const std::string& level) +{ std::string nickname = ConfigManager::getSingleton().getGameValue(Config::NICKNAME, std::string(), false); - if (!nickname.empty()) + if(!nickname.empty()) ODFrameListener::getSingleton().getClientGameMap()->setLocalPlayerNick(nickname); - - tmpWin = getModeManager().getGui().getGuiSheet(Gui::loadSavedGameMenu)->getChild("LoadingText"); + CEGUI::Window* tmpWin = getModeManager().getGui().getGuiSheet(Gui::loadSavedGameMenu)->getChild("LoadingText"); tmpWin->setText("Loading..."); tmpWin->show(); - CEGUI::ListboxItem* selItem = levelSelectList->getFirstSelectedItem(); - int id = selItem->getID(); - - const std::string& level = mFilesList[id]; // In single player mode, we act as a server if(!ODServer::getSingleton().startServer(nickname, level, ServerMode::ModeGameLoaded, false)) { @@ -159,6 +207,7 @@ bool MenuModeLoad::launchSelectedButtonPressed(const CEGUI::EventArgs&) + ResourceManager::getSingleton().buildReplayFilename(); if(!ODClient::getSingleton().connect("localhost", port, timeout, replayFilename)) { + ODServer::getSingleton().stopServer(); OD_LOG_ERR("Could not connect to server for single player game !!!"); tmpWin = getModeManager().getGui().getGuiSheet(Gui::loadSavedGameMenu)->getChild("LoadingText"); tmpWin->setText("Error: Couldn't connect to local server!"); @@ -227,6 +276,7 @@ bool MenuModeLoad::updateDescription(const CEGUI::EventArgs&) mapDescription = "invalid map"; descTxt->setText(mapDescription); + static_cast(descTxt->getChild("__auto_vscrollbar__"))->setScrollPosition(0); return true; } diff --git a/source/modes/MenuModeLoad.h b/source/modes/MenuModeLoad.h index 8b29f49c8..7d2c40f02 100755 --- a/source/modes/MenuModeLoad.h +++ b/source/modes/MenuModeLoad.h @@ -23,7 +23,8 @@ class MenuModeLoad: public AbstractApplicationMode { public: - MenuModeLoad(ModeManager*); + MenuModeLoad(ModeManager*, bool inGame = false, const std::string& savedGame = {}); + ~MenuModeLoad() override; //! \brief Called when the game mode is activated //! Used to call the corresponding Gui Sheet. @@ -32,8 +33,15 @@ class MenuModeLoad: public AbstractApplicationMode bool launchSelectedButtonPressed(const CEGUI::EventArgs&); bool deleteSelectedButtonPressed(const CEGUI::EventArgs&); bool updateDescription(const CEGUI::EventArgs&); + bool closeBrowser(const CEGUI::EventArgs& = {}); + bool isOpenInGame() const { return mInGame && mOpen; } private: + bool launchSavedGame(const std::string& level); + bool mInGame; + bool mOpen = false; + bool mWasPaused = false; + std::string mSavedGame; std::vector mFilesList; }; diff --git a/source/modes/MenuModeMain.cpp b/source/modes/MenuModeMain.cpp index 99fdd8fec..afc1f2e3d 100755 --- a/source/modes/MenuModeMain.cpp +++ b/source/modes/MenuModeMain.cpp @@ -192,6 +192,21 @@ bool MenuModeMain::toggleSettings(const CEGUI::EventArgs&) return true; } +bool MenuModeMain::goBack(const CEGUI::EventArgs&) +{ + CEGUI::Window* mainWin = getModeManager().getGui().getGuiSheet(Gui::mainMenu); + for(const std::string& name : {WINDOW_SKIRMISH, WINDOW_MULTIPLAYER, WINDOW_EDITOR}) + { + CEGUI::Window* window = mainWin->getChild(name); + if(window->isVisible()) + { + window->hide(); + break; + } + } + return true; +} + bool MenuModeMain::toggleSkirmishSubMenu(const CEGUI::EventArgs&) { CEGUI::Window* mainWin = getModeManager().getGui().getGuiSheet(Gui::mainMenu); diff --git a/source/modes/MenuModeMain.h b/source/modes/MenuModeMain.h index 85dbc1f49..09d847ab8 100755 --- a/source/modes/MenuModeMain.h +++ b/source/modes/MenuModeMain.h @@ -34,6 +34,8 @@ class MenuModeMain: public AbstractApplicationMode //! Used to call the corresponding Gui Sheet. void activate() final override; + bool goBack(const CEGUI::EventArgs& e = {}) override; + private: //! \brief The Settings window SettingsWindow mSettings; diff --git a/source/modes/ModeManager.cpp b/source/modes/ModeManager.cpp index 58a1e0a52..0c3a214f8 100755 --- a/source/modes/ModeManager.cpp +++ b/source/modes/ModeManager.cpp @@ -73,6 +73,12 @@ void ModeManager::requestPreviousMode() mStoreCurrentModeAtChange = false; } +void ModeManager::requestSavedGame(const std::string& filename) +{ + mRequestedSavedGame = filename; + requestMode(MENU_LOAD_SAVEDGAME, false); +} + void ModeManager::checkModeChange() { if (mRequestedMode == NONE) @@ -128,7 +134,10 @@ void ModeManager::checkModeChange() mCurrentApplicationMode = Utils::make_unique(this); break; case MENU_LOAD_SAVEDGAME: - mCurrentApplicationMode = Utils::make_unique(this); + mCurrentApplicationMode = Utils::make_unique(this, false, mRequestedSavedGame); + mRequestedSavedGame.clear(); + if(previousMode == GAME) + mPreviousModeTypes.clear(); break; case MENU_MASTERSERVER_HOST: mCurrentApplicationMode = Utils::make_unique(this, true); diff --git a/source/modes/ModeManager.h b/source/modes/ModeManager.h index c4af9873c..2e2895806 100755 --- a/source/modes/ModeManager.h +++ b/source/modes/ModeManager.h @@ -55,6 +55,7 @@ class ModeManager final : public AbstractModeManager //! \brief Request to load the previous mode type. void requestPreviousMode(); + void requestSavedGame(const std::string& filename); InputManager& getInputManager() { return mInputManager; } @@ -83,6 +84,7 @@ class ModeManager final : public AbstractModeManager //! \brief Tells whether the current mode should be kept in history //! when changing from the current mode. bool mStoreCurrentModeAtChange; + std::string mRequestedSavedGame; //! \brief Actually change the mode if needed void checkModeChange(); diff --git a/source/network/ODClient.cpp b/source/network/ODClient.cpp index 50d07e3be..fa304c0d7 100755 --- a/source/network/ODClient.cpp +++ b/source/network/ODClient.cpp @@ -234,9 +234,39 @@ bool ODClient::processMessage(ServerNotificationType cmd, ODPacket& packetReceiv { case ServerMode::ModeGameSinglePlayer: case ServerMode::ModeGameMultiPlayer: - case ServerMode::ModeGameLoaded: frameListener->getModeManager()->requestMode(AbstractModeManager::MENU_CONFIGURE_SEATS); break; + case ServerMode::ModeGameLoaded: + { + // A saved skirmish already fixes its human side, faction and team. + bool fixedSeats = getSource() != ODSource::file; + uint32_t humanSeats = 0; + for(Seat* seat : gameMap->getSeats()) + { + if(seat->isRogueSeat()) + continue; + if(seat->getPlayerType() == Seat::PLAYER_TYPE_HUMAN) + ++humanSeats; + else if(seat->getPlayerType() != Seat::PLAYER_TYPE_AI && + seat->getPlayerType() != Seat::PLAYER_TYPE_INACTIVE) + fixedSeats = false; + if(seat->getAvailableTeamIds().size() != 1 || + seat->getFaction() == Seat::PLAYER_FACTION_CHOICE) + fixedSeats = false; + } + if(fixedSeats && humanSeats == 1) + { + ODPacket ready; + ready << ClientNotificationType::readyForSeatConfiguration; + send(ready); + ODPacket start; + start << ClientNotificationType::seatConfigurationSet; + send(start); + } + else + frameListener->getModeManager()->requestMode(AbstractModeManager::MENU_CONFIGURE_SEATS); + break; + } case ServerMode::ModeEditor: break; default: @@ -273,6 +303,8 @@ bool ODClient::processMessage(ServerNotificationType cmd, ODPacket& packetReceiv case ServerNotificationType::addPlayers: { + if(frameListener->getModeManager()->getCurrentModeType() == ModeManager::MENU_LOAD_SAVEDGAME) + break; if(frameListener->getModeManager()->getCurrentModeType() != ModeManager::ModeType::MENU_CONFIGURE_SEATS) { OD_LOG_ERR("Wrong mode " + Helper::toString(frameListener->getModeManager()->getCurrentModeType()));