From 6c4e1f4e8f06c9a097b3c76d07df67d2299b8cca Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 6 Aug 2023 16:09:41 +0200 Subject: [PATCH] Lua commands world.pause / world.unpause --- apps/openmw/engine.cpp | 32 ++++++++++--------------- apps/openmw/mwlua/luabindings.cpp | 15 ++++++++---- apps/openmw/mwlua/luamanagerimp.cpp | 27 ++++++++++----------- apps/openmw/mwlua/worldview.cpp | 1 - apps/openmw/mwlua/worldview.hpp | 5 ---- apps/openmw/mwworld/datetimemanager.cpp | 15 ++++++++++++ apps/openmw/mwworld/datetimemanager.hpp | 14 +++++++++++ files/lua_api/openmw/world.lua | 15 ++++++++++++ 8 files changed, 80 insertions(+), 44 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 7da3cd78fe..3f7263796d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -201,9 +201,6 @@ bool OMW::Engine::frame(float frametime) mSoundManager->update(frametime); } - // Main menu opened? Then scripts are also paused. - bool paused = mWindowManager->containsMode(MWGui::GM_MainMenu); - { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); // Should be called after input manager update and before any change to the game world. @@ -217,14 +214,14 @@ bool OMW::Engine::frame(float frametime) mStateManager->update(frametime); } - bool guiActive = mWindowManager->isGuiMode(); + bool paused = mWorld->getTimeManager()->isPaused(); { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { - if (!paused) + if (!mWindowManager->containsMode(MWGui::GM_MainMenu)) { if (mWorld->getScriptsEnabled()) { @@ -238,7 +235,7 @@ bool OMW::Engine::frame(float frametime) mWorld->getWorldScene().markCellAsUnchanged(); } - if (!guiActive) + if (!paused) { double hours = (frametime * mWorld->getTimeManager()->getGameTimeScale()) / 3600.0; mWorld->advanceTime(hours, true); @@ -253,13 +250,13 @@ bool OMW::Engine::frame(float frametime) if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { - mMechanicsManager->update(frametime, guiActive); + mMechanicsManager->update(frametime, paused); } if (mStateManager->getState() == MWBase::StateManager::State_Running) { MWWorld::Ptr player = mWorld->getPlayerPtr(); - if (!guiActive && player.getClass().getCreatureStats(player).isDead()) + if (!paused && player.getClass().getCreatureStats(player).isDead()) mStateManager->endGame(); } } @@ -270,7 +267,7 @@ bool OMW::Engine::frame(float frametime) if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { - mWorld->updatePhysics(frametime, guiActive, frameStart, frameNumber, *stats); + mWorld->updatePhysics(frametime, paused, frameStart, frameNumber, *stats); } } @@ -280,7 +277,7 @@ bool OMW::Engine::frame(float frametime) if (mStateManager->getState() != MWBase::StateManager::State_NoGame) { - mWorld->update(frametime, guiActive); + mWorld->update(frametime, paused); } } @@ -928,7 +925,7 @@ void OMW::Engine::go() } // Start the main rendering loop - double simulationTime = 0.0; + MWWorld::DateTimeManager& timeManager = *mWorld->getTimeManager(); Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(mEnvironment.getFrameRateLimit()); const std::chrono::steady_clock::duration maxSimulationInterval(std::chrono::milliseconds(200)); while (!mViewer->done() && !mStateManager->hasQuitRequest()) @@ -936,21 +933,18 @@ void OMW::Engine::go() const double dt = std::chrono::duration_cast>( std::min(frameRateLimiter.getLastFrameDuration(), maxSimulationInterval)) .count() - * mWorld->getTimeManager()->getSimulationTimeScale(); + * timeManager.getSimulationTimeScale(); - mViewer->advance(simulationTime); + mViewer->advance(timeManager.getSimulationTime()); if (!frame(dt)) { std::this_thread::sleep_for(std::chrono::milliseconds(5)); continue; } - else - { - bool guiActive = mWindowManager->isGuiMode(); - if (!guiActive) - simulationTime += dt; - } + timeManager.updateIsPaused(); + if (!timeManager.isPaused()) + timeManager.setSimulationTime(timeManager.getSimulationTime() + dt); if (stats) { diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 5e9da7e6b9..a0809a91b8 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -68,7 +68,7 @@ namespace MWLua api["getSimulationTimeScale"] = [timeManager]() { return timeManager->getSimulationTimeScale(); }; api["getGameTime"] = [timeManager]() { return timeManager->getGameTime(); }; api["getGameTimeScale"] = [timeManager]() { return timeManager->getGameTimeScale(); }; - api["isWorldPaused"] = [world = context.mWorldView]() { return world->isPaused(); }; + api["isWorldPaused"] = [timeManager]() { return timeManager->isPaused(); }; api["getRealTime"] = []() { return std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); }; @@ -81,9 +81,16 @@ namespace MWLua context.mLuaManager->addAction([scale, timeManager] { timeManager->setSimulationTimeScale(scale); }); }; - // TODO: Ability to pause/resume world from Lua (needed for UI dehardcoding) - // api["pause"] = []() {}; - // api["resume"] = []() {}; + api["pause"] + = [timeManager](sol::optional tag) { timeManager->pause(tag.value_or("paused")); }; + api["unpause"] + = [timeManager](sol::optional tag) { timeManager->unpause(tag.value_or("paused")); }; + api["getPausedTags"] = [timeManager](sol::this_state lua) { + sol::table res(lua, sol::create); + for (const std::string& tag : timeManager->getPausedTags()) + res[tag] = tag; + return res; + }; } static sol::table initContentFilesBindings(sol::state_view& lua) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 34900f39a8..b6f38e3a2d 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -128,8 +128,6 @@ namespace MWLua if (mPlayer.isEmpty()) return; // The game is not started yet. - float frameDuration = MWBase::Environment::get().getFrameDuration(); - MWWorld::Ptr newPlayerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); if (!(getId(mPlayer) == getId(newPlayerPtr))) throw std::logic_error("Player RefNum was changed unexpectedly"); @@ -151,16 +149,12 @@ namespace MWLua mLuaEvents.finalizeEventBatch(); - if (!mWorldView.isPaused()) - { // Update time and process timers - MWWorld::DateTimeManager& timeManager = *MWBase::Environment::get().getWorld()->getTimeManager(); - double simulationTime = timeManager.getSimulationTime() + frameDuration; - timeManager.setSimulationTime(simulationTime); - double gameTime = timeManager.getGameTime(); - - mGlobalScripts.processTimers(simulationTime, gameTime); + MWWorld::DateTimeManager& timeManager = *MWBase::Environment::get().getWorld()->getTimeManager(); + if (!timeManager.isPaused()) + { + mGlobalScripts.processTimers(timeManager.getSimulationTime(), timeManager.getGameTime()); for (LocalScripts* scripts : mActiveLocalScripts) - scripts->processTimers(simulationTime, gameTime); + scripts->processTimers(timeManager.getSimulationTime(), timeManager.getGameTime()); } // Run event handlers for events that were sent before `finalizeEventBatch`. @@ -173,8 +167,9 @@ namespace MWLua // Run engine handlers mEngineEvents.callEngineHandlers(); - if (!mWorldView.isPaused()) + if (!timeManager.isPaused()) { + float frameDuration = MWBase::Environment::get().getFrameDuration(); for (LocalScripts* scripts : mActiveLocalScripts) scripts->update(frameDuration); mGlobalScripts.update(frameDuration); @@ -222,17 +217,19 @@ namespace MWLua // We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency. mProcessingInputEvents = true; PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); - if (playerScripts && !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu)) + MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); + if (playerScripts && !windowManager->containsMode(MWGui::GM_MainMenu)) { for (const auto& event : mInputEvents) playerScripts->processInputEvent(event); } mInputEvents.clear(); if (playerScripts) - playerScripts->onFrame(mWorldView.isPaused() ? 0.0 : MWBase::Environment::get().getFrameDuration()); + playerScripts->onFrame(MWBase::Environment::get().getWorld()->getTimeManager()->isPaused() + ? 0.0 + : MWBase::Environment::get().getFrameDuration()); mProcessingInputEvents = false; - MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); for (const std::string& message : mUIMessages) windowManager->messageBox(message); mUIMessages.clear(); diff --git a/apps/openmw/mwlua/worldview.cpp b/apps/openmw/mwlua/worldview.cpp index f80f6a159f..9687869935 100644 --- a/apps/openmw/mwlua/worldview.cpp +++ b/apps/openmw/mwlua/worldview.cpp @@ -24,7 +24,6 @@ namespace MWLua mContainersInScene.updateList(); mDoorsInScene.updateList(); mItemsInScene.updateList(); - mPaused = MWBase::Environment::get().getWindowManager()->isGuiMode(); } void WorldView::clear() diff --git a/apps/openmw/mwlua/worldview.hpp b/apps/openmw/mwlua/worldview.hpp index b00abadc60..dc5f228f74 100644 --- a/apps/openmw/mwlua/worldview.hpp +++ b/apps/openmw/mwlua/worldview.hpp @@ -18,9 +18,6 @@ namespace MWLua void update(); // Should be called every frame. void clear(); // Should be called every time before starting or loading a new game. - // Whether the world is paused (i.e. game time is not changing and actors don't move). - bool isPaused() const { return mPaused; } - ObjectIdList getActivatorsInScene() const { return mActivatorsInScene.mList; } ObjectIdList getActorsInScene() const { return mActorsInScene.mList; } ObjectIdList getContainersInScene() const { return mContainersInScene.mList; } @@ -54,8 +51,6 @@ namespace MWLua ObjectGroup mDoorsInScene; ObjectGroup mItemsInScene; ObjectIdList mPlayers = std::make_shared>(); - - bool mPaused = false; }; } diff --git a/apps/openmw/mwworld/datetimemanager.cpp b/apps/openmw/mwworld/datetimemanager.cpp index f97d1960ca..7559242bef 100644 --- a/apps/openmw/mwworld/datetimemanager.cpp +++ b/apps/openmw/mwworld/datetimemanager.cpp @@ -4,6 +4,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "duration.hpp" @@ -57,6 +58,8 @@ namespace MWWorld mYear = globalVariables[Globals::sYear].getInteger(); mGameTimeScale = globalVariables[Globals::sTimeScale].getFloat(); setSimulationTimeScale(1.0); + mPaused = false; + mPausedTags.clear(); } void DateTimeManager::setHour(double hour) @@ -250,4 +253,16 @@ namespace MWWorld mSimulationTimeScale = std::max(0.f, scale); MWBase::Environment::get().getSoundManager()->setSimulationTimeScale(mSimulationTimeScale); } + + void DateTimeManager::unpause(std::string_view tag) + { + auto it = mPausedTags.find(tag); + if (it != mPausedTags.end()) + mPausedTags.erase(it); + } + + void DateTimeManager::updateIsPaused() + { + mPaused = !mPausedTags.empty() || MWBase::Environment::get().getWindowManager()->isGuiMode(); + } } diff --git a/apps/openmw/mwworld/datetimemanager.hpp b/apps/openmw/mwworld/datetimemanager.hpp index 47609efc26..f89894292f 100644 --- a/apps/openmw/mwworld/datetimemanager.hpp +++ b/apps/openmw/mwworld/datetimemanager.hpp @@ -1,6 +1,7 @@ #ifndef GAME_MWWORLD_DATETIMEMANAGER_H #define GAME_MWWORLD_DATETIMEMANAGER_H +#include #include #include "globalvariablename.hpp" @@ -34,6 +35,17 @@ namespace MWWorld float getSimulationTimeScale() const { return mSimulationTimeScale; } void setSimulationTimeScale(float scale); // simulation time to real time ratio + // Whether the game is paused in the current frame. + bool isPaused() const { return mPaused; } + + // Pauses the game starting from the next frame until `unpause` is called with the same tag. + void pause(std::string_view tag) { mPausedTags.emplace(tag); } + void unpause(std::string_view tag); + const std::set>& getPausedTags() const { return mPausedTags; } + + // Updates mPaused; should be called once a frame. + void updateIsPaused(); + private: friend class World; void setup(Globals& globalVariables); @@ -53,6 +65,8 @@ namespace MWWorld float mGameTimeScale = 0.f; float mSimulationTimeScale = 1.0; double mSimulationTime = 0.0; + bool mPaused = false; + std::set> mPausedTags; }; } diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 26dd9c69d5..53f1e44e26 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -106,6 +106,21 @@ -- @function [parent=#world] isWorldPaused -- @return #boolean +--- +-- Pause the game starting from the next frame. +-- @function [parent=#world] pause +-- @param #string tag (optional) The game will be paused until `unpause` is called with the same tag. + +--- +-- Remove given tag from the list of pause tags. Resume the game starting from the next frame if the list became empty. +-- @function [parent=#world] unpause +-- @param #string tag (optional) Needed to undo `pause` called with this tag. + +--- +-- The tags that are currently pausing the game. +-- @function [parent=#world] getPausedTags +-- @return #table + --- -- Return an object by RefNum/FormId. -- Note: the function always returns @{openmw.core#GameObject} and doesn't validate that