1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-03-30 07:21:12 +00:00

Merge branch 'time' into 'master'

Pause/resume the game in Lua scripts

See merge request OpenMW/openmw!3317
This commit is contained in:
psi29a 2023-08-18 08:22:04 +00:00
commit e21e3a0d46
28 changed files with 242 additions and 249 deletions

View File

@ -59,7 +59,7 @@ add_openmw_dir (mwscript
) )
add_openmw_dir (mwlua add_openmw_dir (mwlua
luamanagerimp object worldview userdataserializer luaevents engineevents objectvariant luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant
context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings
camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats debugbindings camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats debugbindings
types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal

View File

@ -65,6 +65,7 @@
#include "mwsound/soundmanagerimp.hpp" #include "mwsound/soundmanagerimp.hpp"
#include "mwworld/class.hpp" #include "mwworld/class.hpp"
#include "mwworld/datetimemanager.hpp"
#include "mwworld/worldimp.hpp" #include "mwworld/worldimp.hpp"
#include "mwrender/vismask.hpp" #include "mwrender/vismask.hpp"
@ -200,9 +201,6 @@ bool OMW::Engine::frame(float frametime)
mSoundManager->update(frametime); mSoundManager->update(frametime);
} }
// Main menu opened? Then scripts are also paused.
bool paused = mWindowManager->containsMode(MWGui::GM_MainMenu);
{ {
ScopedProfile<UserStatsType::LuaSyncUpdate> profile(frameStart, frameNumber, *timer, *stats); ScopedProfile<UserStatsType::LuaSyncUpdate> profile(frameStart, frameNumber, *timer, *stats);
// Should be called after input manager update and before any change to the game world. // Should be called after input manager update and before any change to the game world.
@ -216,14 +214,14 @@ bool OMW::Engine::frame(float frametime)
mStateManager->update(frametime); mStateManager->update(frametime);
} }
bool guiActive = mWindowManager->isGuiMode(); bool paused = mWorld->getTimeManager()->isPaused();
{ {
ScopedProfile<UserStatsType::Script> profile(frameStart, frameNumber, *timer, *stats); ScopedProfile<UserStatsType::Script> profile(frameStart, frameNumber, *timer, *stats);
if (mStateManager->getState() != MWBase::StateManager::State_NoGame) if (mStateManager->getState() != MWBase::StateManager::State_NoGame)
{ {
if (!paused) if (!mWindowManager->containsMode(MWGui::GM_MainMenu))
{ {
if (mWorld->getScriptsEnabled()) if (mWorld->getScriptsEnabled())
{ {
@ -237,9 +235,9 @@ bool OMW::Engine::frame(float frametime)
mWorld->getWorldScene().markCellAsUnchanged(); mWorld->getWorldScene().markCellAsUnchanged();
} }
if (!guiActive) if (!paused)
{ {
double hours = (frametime * mWorld->getTimeScaleFactor()) / 3600.0; double hours = (frametime * mWorld->getTimeManager()->getGameTimeScale()) / 3600.0;
mWorld->advanceTime(hours, true); mWorld->advanceTime(hours, true);
mWorld->rechargeItems(frametime, true); mWorld->rechargeItems(frametime, true);
} }
@ -252,13 +250,13 @@ bool OMW::Engine::frame(float frametime)
if (mStateManager->getState() != MWBase::StateManager::State_NoGame) if (mStateManager->getState() != MWBase::StateManager::State_NoGame)
{ {
mMechanicsManager->update(frametime, guiActive); mMechanicsManager->update(frametime, paused);
} }
if (mStateManager->getState() == MWBase::StateManager::State_Running) if (mStateManager->getState() == MWBase::StateManager::State_Running)
{ {
MWWorld::Ptr player = mWorld->getPlayerPtr(); MWWorld::Ptr player = mWorld->getPlayerPtr();
if (!guiActive && player.getClass().getCreatureStats(player).isDead()) if (!paused && player.getClass().getCreatureStats(player).isDead())
mStateManager->endGame(); mStateManager->endGame();
} }
} }
@ -269,7 +267,7 @@ bool OMW::Engine::frame(float frametime)
if (mStateManager->getState() != MWBase::StateManager::State_NoGame) if (mStateManager->getState() != MWBase::StateManager::State_NoGame)
{ {
mWorld->updatePhysics(frametime, guiActive, frameStart, frameNumber, *stats); mWorld->updatePhysics(frametime, paused, frameStart, frameNumber, *stats);
} }
} }
@ -279,7 +277,7 @@ bool OMW::Engine::frame(float frametime)
if (mStateManager->getState() != MWBase::StateManager::State_NoGame) if (mStateManager->getState() != MWBase::StateManager::State_NoGame)
{ {
mWorld->update(frametime, guiActive); mWorld->update(frametime, paused);
} }
} }
@ -928,7 +926,7 @@ void OMW::Engine::go()
} }
// Start the main rendering loop // Start the main rendering loop
double simulationTime = 0.0; MWWorld::DateTimeManager& timeManager = *mWorld->getTimeManager();
Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(mEnvironment.getFrameRateLimit()); Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(mEnvironment.getFrameRateLimit());
const std::chrono::steady_clock::duration maxSimulationInterval(std::chrono::milliseconds(200)); const std::chrono::steady_clock::duration maxSimulationInterval(std::chrono::milliseconds(200));
while (!mViewer->done() && !mStateManager->hasQuitRequest()) while (!mViewer->done() && !mStateManager->hasQuitRequest())
@ -936,21 +934,18 @@ void OMW::Engine::go()
const double dt = std::chrono::duration_cast<std::chrono::duration<double>>( const double dt = std::chrono::duration_cast<std::chrono::duration<double>>(
std::min(frameRateLimiter.getLastFrameDuration(), maxSimulationInterval)) std::min(frameRateLimiter.getLastFrameDuration(), maxSimulationInterval))
.count() .count()
* mEnvironment.getWorld()->getSimulationTimeScale(); * timeManager.getSimulationTimeScale();
mViewer->advance(simulationTime); mViewer->advance(timeManager.getSimulationTime());
if (!frame(dt)) if (!frame(dt))
{ {
std::this_thread::sleep_for(std::chrono::milliseconds(5)); std::this_thread::sleep_for(std::chrono::milliseconds(5));
continue; continue;
} }
else timeManager.updateIsPaused();
{ if (!timeManager.isPaused())
bool guiActive = mWindowManager->isGuiMode(); timeManager.setSimulationTime(timeManager.getSimulationTime() + dt);
if (!guiActive)
simulationTime += dt;
}
if (stats) if (stats)
{ {

View File

@ -92,6 +92,7 @@ namespace MWWorld
class ESMStore; class ESMStore;
class RefData; class RefData;
class Cell; class Cell;
class DateTimeManager;
typedef std::vector<std::pair<MWWorld::Ptr, MWMechanics::Movement>> PtrMovementList; typedef std::vector<std::pair<MWWorld::Ptr, MWMechanics::Movement>> PtrMovementList;
} }
@ -209,15 +210,9 @@ namespace MWBase
virtual void advanceTime(double hours, bool incremental = false) = 0; virtual void advanceTime(double hours, bool incremental = false) = 0;
///< Advance in-game time. ///< Advance in-game time.
virtual std::string_view getMonthName(int month = -1) const = 0;
///< Return name of month (-1: current month)
virtual MWWorld::TimeStamp getTimeStamp() const = 0; virtual MWWorld::TimeStamp getTimeStamp() const = 0;
///< Return current in-game time and number of day since new game start. ///< Return current in-game time and number of day since new game start.
virtual ESM::EpochTimeStamp getEpochTimeStamp() const = 0;
///< Return current in-game date and time.
virtual bool toggleSky() = 0; virtual bool toggleSky() = 0;
///< \return Resulting mode ///< \return Resulting mode
@ -239,12 +234,6 @@ namespace MWBase
virtual void modRegion(const ESM::RefId& regionid, const std::vector<char>& chances) = 0; virtual void modRegion(const ESM::RefId& regionid, const std::vector<char>& chances) = 0;
virtual float getTimeScaleFactor() const = 0;
virtual float getSimulationTimeScale() const = 0;
virtual void setSimulationTimeScale(float scale) = 0;
virtual void changeToInteriorCell( virtual void changeToInteriorCell(
std::string_view cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true) std::string_view cellName, const ESM::Position& position, bool adjustPlayerPos, bool changeEvent = true)
= 0; = 0;
@ -612,6 +601,8 @@ namespace MWBase
virtual MWRender::PostProcessor* getPostProcessor() = 0; virtual MWRender::PostProcessor* getPostProcessor() = 0;
virtual MWWorld::DateTimeManager* getTimeManager() = 0;
virtual void setActorActive(const MWWorld::Ptr& ptr, bool value) = 0; virtual void setActorActive(const MWWorld::Ptr& ptr, bool value) = 0;
}; };
} }

View File

@ -13,6 +13,7 @@
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwdialogue/keywordsearch.hpp" #include "../mwdialogue/keywordsearch.hpp"
#include "../mwworld/datetimemanager.hpp"
namespace MWGui namespace MWGui
{ {
@ -253,8 +254,9 @@ namespace MWGui
std::ostringstream os; std::ostringstream os;
os << itr->mDayOfMonth << ' ' << MWBase::Environment::get().getWorld()->getMonthName(itr->mMonth) os << itr->mDayOfMonth << ' '
<< " (" << dayStr << " " << (itr->mDay) << ')'; << MWBase::Environment::get().getWorld()->getTimeManager()->getMonthName(itr->mMonth) << " ("
<< dayStr << " " << (itr->mDay) << ')';
timestamp_buffer = os.str(); timestamp_buffer = os.str();
} }

View File

@ -29,6 +29,7 @@
#include "../mwbase/statemanager.hpp" #include "../mwbase/statemanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwstate/character.hpp" #include "../mwstate/character.hpp"
@ -422,8 +423,9 @@ namespace MWGui
hour = 12; hour = 12;
text << mCurrentSlot->mProfile.mInGameTime.mDay << " " text << mCurrentSlot->mProfile.mInGameTime.mDay << " "
<< MWBase::Environment::get().getWorld()->getMonthName(mCurrentSlot->mProfile.mInGameTime.mMonth) << " " << MWBase::Environment::get().getWorld()->getTimeManager()->getMonthName(
<< hour << " " << (pm ? "#{Calendar:pm}" : "#{Calendar:am}"); mCurrentSlot->mProfile.mInGameTime.mMonth)
<< " " << hour << " " << (pm ? "#{Calendar:pm}" : "#{Calendar:am}");
if (Settings::Manager::getBool("timeplayed", "Saves")) if (Settings::Manager::getBool("timeplayed", "Saves"))
{ {

View File

@ -19,6 +19,7 @@
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/actorutil.hpp"
@ -154,17 +155,17 @@ namespace MWGui
onHourSliderChangedPosition(mHourSlider, 0); onHourSliderChangedPosition(mHourSlider, 0);
mHourSlider->setScrollPosition(0); mHourSlider->setScrollPosition(0);
std::string_view month = MWBase::Environment::get().getWorld()->getMonthName(); const MWWorld::DateTimeManager& timeManager = *MWBase::Environment::get().getWorld()->getTimeManager();
int hour = static_cast<int>(MWBase::Environment::get().getWorld()->getTimeStamp().getHour()); std::string_view month = timeManager.getMonthName();
int hour = static_cast<int>(timeManager.getTimeStamp().getHour());
bool pm = hour >= 12; bool pm = hour >= 12;
if (hour >= 13) if (hour >= 13)
hour -= 12; hour -= 12;
if (hour == 0) if (hour == 0)
hour = 12; hour = 12;
ESM::EpochTimeStamp currentDate = MWBase::Environment::get().getWorld()->getEpochTimeStamp(); ESM::EpochTimeStamp currentDate = timeManager.getEpochTimeStamp();
std::string daysPassed = Misc::StringUtils::format( std::string daysPassed = Misc::StringUtils::format("(#{Calendar:day} %i)", timeManager.getTimeStamp().getDay());
"(#{Calendar:day} %i)", MWBase::Environment::get().getWorld()->getTimeStamp().getDay());
std::string_view formattedHour(pm ? "#{Calendar:pm}" : "#{Calendar:am}"); std::string_view formattedHour(pm ? "#{Calendar:pm}" : "#{Calendar:am}");
std::string dateTimeText std::string dateTimeText
= Misc::StringUtils::format("%i %s %s %i %s", currentDate.mDay, month, daysPassed, hour, formattedHour); = Misc::StringUtils::format("%i %s %s %i %s", currentDate.mDay, month, daysPassed, hour, formattedHour);

View File

@ -11,7 +11,7 @@ namespace MWLua
{ {
class LuaEvents; class LuaEvents;
class LuaManager; class LuaManager;
class WorldView; class ObjectLists;
struct Context struct Context
{ {
@ -19,7 +19,7 @@ namespace MWLua
LuaManager* mLuaManager; LuaManager* mLuaManager;
LuaUtil::LuaState* mLua; LuaUtil::LuaState* mLua;
LuaUtil::UserdataSerializer* mSerializer; LuaUtil::UserdataSerializer* mSerializer;
WorldView* mWorldView; ObjectLists* mObjectLists;
LuaEvents* mLuaEvents; LuaEvents* mLuaEvents;
}; };

View File

@ -20,6 +20,7 @@
#include "../mwbase/statemanager.hpp" #include "../mwbase/statemanager.hpp"
#include "../mwworld/action.hpp" #include "../mwworld/action.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/manualref.hpp" #include "../mwworld/manualref.hpp"
#include "../mwworld/store.hpp" #include "../mwworld/store.hpp"
@ -28,7 +29,7 @@
#include "luaevents.hpp" #include "luaevents.hpp"
#include "luamanagerimp.hpp" #include "luamanagerimp.hpp"
#include "mwscriptbindings.hpp" #include "mwscriptbindings.hpp"
#include "worldview.hpp" #include "objectlists.hpp"
#include "camerabindings.hpp" #include "camerabindings.hpp"
#include "cellbindings.hpp" #include "cellbindings.hpp"
@ -61,13 +62,13 @@ namespace MWLua
static void addTimeBindings(sol::table& api, const Context& context, bool global) static void addTimeBindings(sol::table& api, const Context& context, bool global)
{ {
MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::DateTimeManager* timeManager = MWBase::Environment::get().getWorld()->getTimeManager();
api["getSimulationTime"] = [world = context.mWorldView]() { return world->getSimulationTime(); }; api["getSimulationTime"] = [timeManager]() { return timeManager->getSimulationTime(); };
api["getSimulationTimeScale"] = [world]() { return world->getSimulationTimeScale(); }; api["getSimulationTimeScale"] = [timeManager]() { return timeManager->getSimulationTimeScale(); };
api["getGameTime"] = [world = context.mWorldView]() { return world->getGameTime(); }; api["getGameTime"] = [timeManager]() { return timeManager->getGameTime(); };
api["getGameTimeScale"] = [world = context.mWorldView]() { return world->getGameTimeScale(); }; api["getGameTimeScale"] = [timeManager]() { return timeManager->getGameTimeScale(); };
api["isWorldPaused"] = [world = context.mWorldView]() { return world->isPaused(); }; api["isWorldPaused"] = [timeManager]() { return timeManager->isPaused(); };
api["getRealTime"] = []() { api["getRealTime"] = []() {
return std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count(); return std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count();
}; };
@ -75,15 +76,21 @@ namespace MWLua
if (!global) if (!global)
return; return;
api["setGameTimeScale"] = [world = context.mWorldView](double scale) { world->setGameTimeScale(scale); }; api["setGameTimeScale"] = [timeManager](double scale) { timeManager->setGameTimeScale(scale); };
api["setSimulationTimeScale"] = [context, timeManager](float scale) {
api["setSimulationTimeScale"] = [context, world](float scale) { context.mLuaManager->addAction([scale, timeManager] { timeManager->setSimulationTimeScale(scale); });
context.mLuaManager->addAction([scale, world] { world->setSimulationTimeScale(scale); });
}; };
// TODO: Ability to pause/resume world from Lua (needed for UI dehardcoding) api["pause"]
// api["pause"] = []() {}; = [timeManager](sol::optional<std::string_view> tag) { timeManager->pause(tag.value_or("paused")); };
// api["resume"] = []() {}; api["unpause"]
= [timeManager](sol::optional<std::string_view> 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) static sol::table initContentFilesBindings(sol::state_view& lua)
@ -228,12 +235,12 @@ namespace MWLua
static sol::table initWorldPackage(const Context& context) static sol::table initWorldPackage(const Context& context)
{ {
sol::table api(context.mLua->sol(), sol::create); sol::table api(context.mLua->sol(), sol::create);
WorldView* worldView = context.mWorldView; ObjectLists* objectLists = context.mObjectLists;
addTimeBindings(api, context, true); addTimeBindings(api, context, true);
addCellGetters(api, context); addCellGetters(api, context);
api["mwscript"] = initMWScriptBindings(context); api["mwscript"] = initMWScriptBindings(context);
api["activeActors"] = GObjectList{ worldView->getActorsInScene() }; api["activeActors"] = GObjectList{ objectLists->getActorsInScene() };
api["players"] = GObjectList{ worldView->getPlayers() }; api["players"] = GObjectList{ objectLists->getPlayers() };
api["createObject"] = [](std::string_view recordId, sol::optional<int> count) -> GObject { api["createObject"] = [](std::string_view recordId, sol::optional<int> count) -> GObject {
MWWorld::ManualRef mref(*MWBase::Environment::get().getESMStore(), ESM::RefId::deserializeText(recordId)); MWWorld::ManualRef mref(*MWBase::Environment::get().getESMStore(), ESM::RefId::deserializeText(recordId));
const MWWorld::Ptr& ptr = mref.getPtr(); const MWWorld::Ptr& ptr = mref.getPtr();
@ -291,11 +298,11 @@ namespace MWLua
std::map<std::string, sol::object> initCommonPackages(const Context& context) std::map<std::string, sol::object> initCommonPackages(const Context& context)
{ {
sol::state_view lua = context.mLua->sol(); sol::state_view lua = context.mLua->sol();
WorldView* w = context.mWorldView; MWWorld::DateTimeManager* tm = MWBase::Environment::get().getWorld()->getTimeManager();
return { return {
{ "openmw.async", { "openmw.async",
LuaUtil::getAsyncPackageInitializer( LuaUtil::getAsyncPackageInitializer(
lua, [w] { return w->getSimulationTime(); }, [w] { return w->getGameTime(); }) }, lua, [tm] { return tm->getSimulationTime(); }, [tm] { return tm->getGameTime(); }) },
{ "openmw.core", initCorePackage(context) }, { "openmw.core", initCorePackage(context) },
{ "openmw.types", initTypesPackage(context) }, { "openmw.types", initTypesPackage(context) },
{ "openmw.util", LuaUtil::initUtilPackage(lua) }, { "openmw.util", LuaUtil::initUtilPackage(lua) },

View File

@ -23,6 +23,7 @@
#include "../mwrender/postprocessor.hpp" #include "../mwrender/postprocessor.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/ptr.hpp" #include "../mwworld/ptr.hpp"
#include "../mwworld/scene.hpp" #include "../mwworld/scene.hpp"
@ -75,7 +76,7 @@ namespace MWLua
context.mIsGlobal = true; context.mIsGlobal = true;
context.mLuaManager = this; context.mLuaManager = this;
context.mLua = &mLua; context.mLua = &mLua;
context.mWorldView = &mWorldView; context.mObjectLists = &mObjectLists;
context.mLuaEvents = &mLuaEvents; context.mLuaEvents = &mLuaEvents;
context.mSerializer = mGlobalSerializer.get(); context.mSerializer = mGlobalSerializer.get();
@ -127,8 +128,6 @@ namespace MWLua
if (mPlayer.isEmpty()) if (mPlayer.isEmpty())
return; // The game is not started yet. return; // The game is not started yet.
float frameDuration = MWBase::Environment::get().getFrameDuration();
MWWorld::Ptr newPlayerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::Ptr newPlayerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr();
if (!(getId(mPlayer) == getId(newPlayerPtr))) if (!(getId(mPlayer) == getId(newPlayerPtr)))
throw std::logic_error("Player RefNum was changed unexpectedly"); throw std::logic_error("Player RefNum was changed unexpectedly");
@ -138,7 +137,7 @@ namespace MWLua
MWBase::Environment::get().getWorldModel()->registerPtr(mPlayer); MWBase::Environment::get().getWorldModel()->registerPtr(mPlayer);
} }
mWorldView.update(); mObjectLists.update();
std::erase_if(mActiveLocalScripts, [](const LocalScripts* l) { std::erase_if(mActiveLocalScripts, [](const LocalScripts* l) {
return l->getPtrOrEmpty().isEmpty() || l->getPtrOrEmpty().getRefData().isDeleted(); return l->getPtrOrEmpty().isEmpty() || l->getPtrOrEmpty().getRefData().isDeleted();
@ -150,15 +149,12 @@ namespace MWLua
mLuaEvents.finalizeEventBatch(); mLuaEvents.finalizeEventBatch();
if (!mWorldView.isPaused()) MWWorld::DateTimeManager& timeManager = *MWBase::Environment::get().getWorld()->getTimeManager();
{ // Update time and process timers if (!timeManager.isPaused())
double simulationTime = mWorldView.getSimulationTime() + frameDuration; {
mWorldView.setSimulationTime(simulationTime); mGlobalScripts.processTimers(timeManager.getSimulationTime(), timeManager.getGameTime());
double gameTime = mWorldView.getGameTime();
mGlobalScripts.processTimers(simulationTime, gameTime);
for (LocalScripts* scripts : mActiveLocalScripts) for (LocalScripts* scripts : mActiveLocalScripts)
scripts->processTimers(simulationTime, gameTime); scripts->processTimers(timeManager.getSimulationTime(), timeManager.getGameTime());
} }
// Run event handlers for events that were sent before `finalizeEventBatch`. // Run event handlers for events that were sent before `finalizeEventBatch`.
@ -171,8 +167,9 @@ namespace MWLua
// Run engine handlers // Run engine handlers
mEngineEvents.callEngineHandlers(); mEngineEvents.callEngineHandlers();
if (!mWorldView.isPaused()) if (!timeManager.isPaused())
{ {
float frameDuration = MWBase::Environment::get().getFrameDuration();
for (LocalScripts* scripts : mActiveLocalScripts) for (LocalScripts* scripts : mActiveLocalScripts)
scripts->update(frameDuration); scripts->update(frameDuration);
mGlobalScripts.update(frameDuration); mGlobalScripts.update(frameDuration);
@ -220,17 +217,19 @@ namespace MWLua
// We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency. // We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency.
mProcessingInputEvents = true; mProcessingInputEvents = true;
PlayerScripts* playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts()); PlayerScripts* playerScripts = dynamic_cast<PlayerScripts*>(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) for (const auto& event : mInputEvents)
playerScripts->processInputEvent(event); playerScripts->processInputEvent(event);
} }
mInputEvents.clear(); mInputEvents.clear();
if (playerScripts) 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; mProcessingInputEvents = false;
MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager();
for (const std::string& message : mUIMessages) for (const std::string& message : mUIMessages)
windowManager->messageBox(message); windowManager->messageBox(message);
mUIMessages.clear(); mUIMessages.clear();
@ -262,7 +261,7 @@ namespace MWLua
mLuaEvents.clear(); mLuaEvents.clear();
mEngineEvents.clear(); mEngineEvents.clear();
mInputEvents.clear(); mInputEvents.clear();
mWorldView.clear(); mObjectLists.clear();
mGlobalScripts.removeAllScripts(); mGlobalScripts.removeAllScripts();
mGlobalScriptsStarted = false; mGlobalScriptsStarted = false;
mNewGameStarted = false; mNewGameStarted = false;
@ -284,8 +283,8 @@ namespace MWLua
return; return;
if (!mPlayer.isEmpty()) if (!mPlayer.isEmpty())
throw std::logic_error("Player is initialized twice"); throw std::logic_error("Player is initialized twice");
mWorldView.objectAddedToScene(ptr); mObjectLists.objectAddedToScene(ptr);
mWorldView.setPlayer(ptr); mObjectLists.setPlayer(ptr);
mPlayer = ptr; mPlayer = ptr;
LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
if (!localScripts) if (!localScripts)
@ -314,7 +313,7 @@ namespace MWLua
void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)
{ {
mWorldView.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet.
mEngineEvents.addToQueue(EngineEvents::OnActive{ getId(ptr) }); mEngineEvents.addToQueue(EngineEvents::OnActive{ getId(ptr) });
LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
@ -334,7 +333,7 @@ namespace MWLua
void LuaManager::objectRemovedFromScene(const MWWorld::Ptr& ptr) void LuaManager::objectRemovedFromScene(const MWWorld::Ptr& ptr)
{ {
mWorldView.objectRemovedFromScene(ptr); mObjectLists.objectRemovedFromScene(ptr);
LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
if (localScripts) if (localScripts)
{ {
@ -400,7 +399,8 @@ namespace MWLua
{ {
writer.startRecord(ESM::REC_LUAM); writer.startRecord(ESM::REC_LUAM);
mWorldView.save(writer); writer.writeHNT<double>("LUAW", MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime());
writer.writeFormId(MWBase::Environment::get().getWorldModel()->getLastGeneratedRefNum(), true);
ESM::LuaScripts globalScripts; ESM::LuaScripts globalScripts;
mGlobalScripts.save(globalScripts); mGlobalScripts.save(globalScripts);
globalScripts.save(writer); globalScripts.save(writer);
@ -414,7 +414,14 @@ namespace MWLua
if (type != ESM::REC_LUAM) if (type != ESM::REC_LUAM)
throw std::runtime_error("ESM::REC_LUAM is expected"); throw std::runtime_error("ESM::REC_LUAM is expected");
mWorldView.load(reader); double simulationTime;
reader.getHNT(simulationTime, "LUAW");
MWBase::Environment::get().getWorld()->getTimeManager()->setSimulationTime(simulationTime);
ESM::FormId lastGenerated = reader.getFormId(true);
if (lastGenerated.hasContentFile())
throw std::runtime_error("Last generated RefNum is invalid");
MWBase::Environment::get().getWorldModel()->setLastGeneratedRefNum(lastGenerated);
ESM::LuaScripts globalScripts; ESM::LuaScripts globalScripts;
globalScripts.load(reader); globalScripts.load(reader);
mLuaEvents.load(mLua.sol(), reader, mContentFileMapping, mGlobalLoader.get()); mLuaEvents.load(mLua.sol(), reader, mContentFileMapping, mGlobalLoader.get());

View File

@ -3,6 +3,7 @@
#include <filesystem> #include <filesystem>
#include <map> #include <map>
#include <osg/Stats>
#include <set> #include <set>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
@ -17,7 +18,7 @@
#include "localscripts.hpp" #include "localscripts.hpp"
#include "luaevents.hpp" #include "luaevents.hpp"
#include "object.hpp" #include "object.hpp"
#include "worldview.hpp" #include "objectlists.hpp"
namespace MWLua namespace MWLua
{ {
@ -156,7 +157,7 @@ namespace MWLua
GlobalScripts mGlobalScripts{ &mLua }; GlobalScripts mGlobalScripts{ &mLua };
std::set<LocalScripts*> mActiveLocalScripts; std::set<LocalScripts*> mActiveLocalScripts;
WorldView mWorldView; ObjectLists mObjectLists;
MWWorld::Ptr mPlayer; MWWorld::Ptr mPlayer;

View File

@ -10,7 +10,7 @@
#include "../mwphysics/raycasting.hpp" #include "../mwphysics/raycasting.hpp"
#include "luamanagerimp.hpp" #include "luamanagerimp.hpp"
#include "worldview.hpp" #include "objectlists.hpp"
namespace sol namespace sol
{ {
@ -25,7 +25,7 @@ namespace MWLua
sol::table initNearbyPackage(const Context& context) sol::table initNearbyPackage(const Context& context)
{ {
sol::table api(context.mLua->sol(), sol::create); sol::table api(context.mLua->sol(), sol::create);
WorldView* worldView = context.mWorldView; ObjectLists* objectLists = context.mObjectLists;
sol::usertype<MWPhysics::RayCastingResult> rayResult sol::usertype<MWPhysics::RayCastingResult> rayResult
= context.mLua->sol().new_usertype<MWPhysics::RayCastingResult>("RayCastingResult"); = context.mLua->sol().new_usertype<MWPhysics::RayCastingResult>("RayCastingResult");
@ -131,12 +131,12 @@ namespace MWLua
return LObject(refId.getIf<ESM::FormIdRefId>()->getValue()); return LObject(refId.getIf<ESM::FormIdRefId>()->getValue());
}; };
api["activators"] = LObjectList{ worldView->getActivatorsInScene() }; api["activators"] = LObjectList{ objectLists->getActivatorsInScene() };
api["actors"] = LObjectList{ worldView->getActorsInScene() }; api["actors"] = LObjectList{ objectLists->getActorsInScene() };
api["containers"] = LObjectList{ worldView->getContainersInScene() }; api["containers"] = LObjectList{ objectLists->getContainersInScene() };
api["doors"] = LObjectList{ worldView->getDoorsInScene() }; api["doors"] = LObjectList{ objectLists->getDoorsInScene() };
api["items"] = LObjectList{ worldView->getItemsInScene() }; api["items"] = LObjectList{ objectLists->getItemsInScene() };
api["players"] = LObjectList{ worldView->getPlayers() }; api["players"] = LObjectList{ objectLists->getPlayers() };
api["NAVIGATOR_FLAGS"] api["NAVIGATOR_FLAGS"]
= LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs<std::string_view, DetourNavigator::Flag>({ = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs<std::string_view, DetourNavigator::Flag>({

View File

@ -1,4 +1,4 @@
#include "worldview.hpp" #include "objectlists.hpp"
#include <components/esm3/esmreader.hpp> #include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp> #include <components/esm3/esmwriter.hpp>
@ -6,28 +6,27 @@
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwclass/container.hpp" #include "../mwclass/container.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/timestamp.hpp"
#include "../mwworld/worldmodel.hpp" #include "../mwworld/worldmodel.hpp"
namespace MWLua namespace MWLua
{ {
void WorldView::update() void ObjectLists::update()
{ {
mActivatorsInScene.updateList(); mActivatorsInScene.updateList();
mActorsInScene.updateList(); mActorsInScene.updateList();
mContainersInScene.updateList(); mContainersInScene.updateList();
mDoorsInScene.updateList(); mDoorsInScene.updateList();
mItemsInScene.updateList(); mItemsInScene.updateList();
mPaused = MWBase::Environment::get().getWindowManager()->isGuiMode();
} }
void WorldView::clear() void ObjectLists::clear()
{ {
mActivatorsInScene.clear(); mActivatorsInScene.clear();
mActorsInScene.clear(); mActorsInScene.clear();
@ -36,7 +35,7 @@ namespace MWLua
mItemsInScene.clear(); mItemsInScene.clear();
} }
WorldView::ObjectGroup* WorldView::chooseGroup(const MWWorld::Ptr& ptr) ObjectLists::ObjectGroup* ObjectLists::chooseGroup(const MWWorld::Ptr& ptr)
{ {
// It is important to check `isMarker` first. // It is important to check `isMarker` first.
// For example "prisonmarker" has class "Door" despite that it is only an invisible marker. // For example "prisonmarker" has class "Door" despite that it is only an invisible marker.
@ -56,7 +55,7 @@ namespace MWLua
return nullptr; return nullptr;
} }
void WorldView::objectAddedToScene(const MWWorld::Ptr& ptr) void ObjectLists::objectAddedToScene(const MWWorld::Ptr& ptr)
{ {
MWBase::Environment::get().getWorldModel()->registerPtr(ptr); MWBase::Environment::get().getWorldModel()->registerPtr(ptr);
ObjectGroup* group = chooseGroup(ptr); ObjectGroup* group = chooseGroup(ptr);
@ -64,36 +63,14 @@ namespace MWLua
addToGroup(*group, ptr); addToGroup(*group, ptr);
} }
void WorldView::objectRemovedFromScene(const MWWorld::Ptr& ptr) void ObjectLists::objectRemovedFromScene(const MWWorld::Ptr& ptr)
{ {
ObjectGroup* group = chooseGroup(ptr); ObjectGroup* group = chooseGroup(ptr);
if (group) if (group)
removeFromGroup(*group, ptr); removeFromGroup(*group, ptr);
} }
double WorldView::getGameTime() const void ObjectLists::ObjectGroup::updateList()
{
MWBase::World* world = MWBase::Environment::get().getWorld();
MWWorld::TimeStamp timeStamp = world->getTimeStamp();
return (static_cast<double>(timeStamp.getDay()) * 24 + timeStamp.getHour()) * 3600.0;
}
void WorldView::load(ESM::ESMReader& esm)
{
esm.getHNT(mSimulationTime, "LUAW");
ESM::FormId lastGenerated = esm.getFormId(true);
if (lastGenerated.hasContentFile())
throw std::runtime_error("Last generated RefNum is invalid");
MWBase::Environment::get().getWorldModel()->setLastGeneratedRefNum(lastGenerated);
}
void WorldView::save(ESM::ESMWriter& esm) const
{
esm.writeHNT("LUAW", mSimulationTime);
esm.writeFormId(MWBase::Environment::get().getWorldModel()->getLastGeneratedRefNum(), true);
}
void WorldView::ObjectGroup::updateList()
{ {
if (mChanged) if (mChanged)
{ {
@ -104,20 +81,20 @@ namespace MWLua
} }
} }
void WorldView::ObjectGroup::clear() void ObjectLists::ObjectGroup::clear()
{ {
mChanged = false; mChanged = false;
mList->clear(); mList->clear();
mSet.clear(); mSet.clear();
} }
void WorldView::addToGroup(ObjectGroup& group, const MWWorld::Ptr& ptr) void ObjectLists::addToGroup(ObjectGroup& group, const MWWorld::Ptr& ptr)
{ {
group.mSet.insert(getId(ptr)); group.mSet.insert(getId(ptr));
group.mChanged = true; group.mChanged = true;
} }
void WorldView::removeFromGroup(ObjectGroup& group, const MWWorld::Ptr& ptr) void ObjectLists::removeFromGroup(ObjectGroup& group, const MWWorld::Ptr& ptr)
{ {
group.mSet.erase(getId(ptr)); group.mSet.erase(getId(ptr));
group.mChanged = true; group.mChanged = true;

View File

@ -1,51 +1,20 @@
#ifndef MWLUA_WORLDVIEW_H #ifndef MWLUA_OBJECTLISTS_H
#define MWLUA_WORLDVIEW_H #define MWLUA_OBJECTLISTS_H
#include "object.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/globals.hpp"
#include <set> #include <set>
namespace ESM #include "object.hpp"
{
class ESMWriter;
class ESMReader;
}
namespace MWLua namespace MWLua
{ {
// WorldView is a kind of an extension to mwworld. It was created on initial stage of // ObjectLists is used to track lists of game objects like nearby.items, nearby.actors, etc.
// OpenMW Lua development in order to minimize the risk of merge conflicts. class ObjectLists
// TODO: Move get*InScene functions to mwworld/scene
// TODO: Move time-related stuff to mwworld; maybe create a new class TimeManager.
// TODO: Remove WorldView.
class WorldView
{ {
public: public:
void update(); // Should be called every frame. void update(); // Should be called every frame.
void clear(); // Should be called every time before starting or loading a new game. 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; }
// The number of seconds passed from the beginning of the game.
double getSimulationTime() const { return mSimulationTime; }
void setSimulationTime(double t) { mSimulationTime = t; }
// The game time (in game seconds) passed from the beginning of the game.
// Note that game time generally goes faster than the simulation time.
double getGameTime() const;
double getGameTimeScale() const { return MWBase::Environment::get().getWorld()->getTimeScaleFactor(); }
void setGameTimeScale(double s)
{
MWBase::Environment::get().getWorld()->setGlobalFloat(MWWorld::Globals::sTimeScale, s);
}
ObjectIdList getActivatorsInScene() const { return mActivatorsInScene.mList; } ObjectIdList getActivatorsInScene() const { return mActivatorsInScene.mList; }
ObjectIdList getActorsInScene() const { return mActorsInScene.mList; } ObjectIdList getActorsInScene() const { return mActorsInScene.mList; }
ObjectIdList getContainersInScene() const { return mContainersInScene.mList; } ObjectIdList getContainersInScene() const { return mContainersInScene.mList; }
@ -58,9 +27,6 @@ namespace MWLua
void setPlayer(const MWWorld::Ptr& player) { *mPlayers = { getId(player) }; } void setPlayer(const MWWorld::Ptr& player) { *mPlayers = { getId(player) }; }
void load(ESM::ESMReader& esm);
void save(ESM::ESMWriter& esm) const;
private: private:
struct ObjectGroup struct ObjectGroup
{ {
@ -82,11 +48,8 @@ namespace MWLua
ObjectGroup mDoorsInScene; ObjectGroup mDoorsInScene;
ObjectGroup mItemsInScene; ObjectGroup mItemsInScene;
ObjectIdList mPlayers = std::make_shared<std::vector<ObjectId>>(); ObjectIdList mPlayers = std::make_shared<std::vector<ObjectId>>();
double mSimulationTime = 0;
bool mPaused = false;
}; };
} }
#endif // MWLUA_WORLDVIEW_H #endif // MWLUA_OBJECTLISTS_H

View File

@ -1,6 +1,7 @@
#include "postprocessingbindings.hpp" #include "postprocessingbindings.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwrender/postprocessor.hpp" #include "../mwrender/postprocessor.hpp"
#include "luamanagerimp.hpp" #include "luamanagerimp.hpp"

View File

@ -13,6 +13,7 @@
#include "localscripts.hpp" #include "localscripts.hpp"
#include "luamanagerimp.hpp" #include "luamanagerimp.hpp"
#include "../mwbase/environment.hpp"
#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/npcstats.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"

View File

@ -14,6 +14,7 @@
#include "context.hpp" #include "context.hpp"
#include "luamanagerimp.hpp" #include "luamanagerimp.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
namespace MWLua namespace MWLua

View File

@ -20,6 +20,7 @@
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "../mwworld/player.hpp" #include "../mwworld/player.hpp"
@ -837,7 +838,7 @@ namespace MWMechanics
// Take a maximum remaining duration of Stunted Magicka effects (-1 is a constant one) in game hours. // Take a maximum remaining duration of Stunted Magicka effects (-1 is a constant one) in game hours.
if (remainingTime > 0) if (remainingTime > 0)
{ {
double timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); double timeScale = MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale();
if (timeScale == 0.0) if (timeScale == 0.0)
timeScale = 1; timeScale = 1;
@ -1864,7 +1865,7 @@ namespace MWMechanics
void Actors::rest(double hours, bool sleep) const void Actors::rest(double hours, bool sleep) const
{ {
float duration = hours * 3600.f; float duration = hours * 3600.f;
const float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); const float timeScale = MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale();
if (timeScale != 0.f) if (timeScale != 0.f)
duration /= timeScale; duration /= timeScale;

View File

@ -10,6 +10,7 @@
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "character.hpp" #include "character.hpp"
#include "creaturestats.hpp" #include "creaturestats.hpp"
@ -72,7 +73,8 @@ namespace MWMechanics
// and the duration is not infinite, the package is complete. // and the duration is not infinite, the package is complete.
if (mDuration > 0) if (mDuration > 0)
{ {
mRemainingDuration -= ((duration * MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); mRemainingDuration
-= ((duration * MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale()) / 3600);
if (mRemainingDuration <= 0) if (mRemainingDuration <= 0)
{ {
mRemainingDuration = mDuration; mRemainingDuration = mDuration;

View File

@ -10,6 +10,7 @@
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "character.hpp" #include "character.hpp"
#include "creaturestats.hpp" #include "creaturestats.hpp"
@ -156,7 +157,9 @@ namespace MWMechanics
// Check if we've run out of time // Check if we've run out of time
if (mDuration > 0) if (mDuration > 0)
{ {
mRemainingDuration -= ((duration * MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); mRemainingDuration
-= ((duration * MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale())
/ 3600);
if (mRemainingDuration <= 0) if (mRemainingDuration <= 0)
{ {
mRemainingDuration = mDuration; mRemainingDuration = mDuration;

View File

@ -16,6 +16,7 @@
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwphysics/raycasting.hpp" #include "../mwphysics/raycasting.hpp"
@ -200,7 +201,8 @@ namespace MWMechanics
// get or create temporary storage // get or create temporary storage
AiWanderStorage& storage = state.get<AiWanderStorage>(); AiWanderStorage& storage = state.get<AiWanderStorage>();
mRemainingDuration -= ((duration * MWBase::Environment::get().getWorld()->getTimeScaleFactor()) / 3600); mRemainingDuration
-= ((duration * MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale()) / 3600);
cStats.setDrawState(DrawState::Nothing); cStats.setDrawState(DrawState::Nothing);
cStats.setMovementFlag(CreatureStats::Flag_Run, false); cStats.setMovementFlag(CreatureStats::Flag_Run, false);

View File

@ -27,6 +27,7 @@
#include <components/nifosg/particle.hpp> #include <components/nifosg/particle.hpp>
#include "../mwworld/datetimemanager.hpp"
#include "../mwworld/weather.hpp" #include "../mwworld/weather.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
@ -559,7 +560,7 @@ namespace MWRender
} }
// rotate the stars by 360 degrees every 4 days // rotate the stars by 360 degrees every 4 days
mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeScaleFactor() * duration mAtmosphereNightRoll += MWBase::Environment::get().getWorld()->getTimeManager()->getGameTimeScale() * duration
* osg::DegreesToRadians(360.f) / (3600 * 96.f); * osg::DegreesToRadians(360.f) / (3600 * 96.f);
if (mAtmosphereNightNode->getNodeMask() != 0) if (mAtmosphereNightNode->getNodeMask() != 0)
mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0, 0, 1))); mAtmosphereNightNode->setAttitude(osg::Quat(mAtmosphereNightRoll, osg::Vec3f(0, 0, 1)));

View File

@ -33,6 +33,7 @@
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/globals.hpp" #include "../mwworld/globals.hpp"
#include "../mwworld/scene.hpp" #include "../mwworld/scene.hpp"
@ -227,7 +228,7 @@ void MWState::StateManager::saveGame(std::string_view description, const Slot* s
profile.mPlayerClassId = classId; profile.mPlayerClassId = classId;
profile.mPlayerCellName = world.getCellName(); profile.mPlayerCellName = world.getCellName();
profile.mInGameTime = world.getEpochTimeStamp(); profile.mInGameTime = world.getTimeManager()->getEpochTimeStamp();
profile.mTimePlayed = mTimePlayed; profile.mTimePlayed = mTimePlayed;
profile.mDescription = description; profile.mDescription = description;

View File

@ -3,6 +3,9 @@
#include <components/l10n/manager.hpp> #include <components/l10n/manager.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "duration.hpp" #include "duration.hpp"
#include "globals.hpp" #include "globals.hpp"
@ -53,7 +56,10 @@ namespace MWWorld
mDay = globalVariables[Globals::sDay].getInteger(); mDay = globalVariables[Globals::sDay].getInteger();
mMonth = globalVariables[Globals::sMonth].getInteger(); mMonth = globalVariables[Globals::sMonth].getInteger();
mYear = globalVariables[Globals::sYear].getInteger(); mYear = globalVariables[Globals::sYear].getInteger();
mTimeScale = globalVariables[Globals::sTimeScale].getFloat(); mGameTimeScale = globalVariables[Globals::sTimeScale].getFloat();
setSimulationTimeScale(1.0);
mPaused = false;
mPausedTags.clear();
} }
void DateTimeManager::setHour(double hour) void DateTimeManager::setHour(double hour)
@ -103,9 +109,9 @@ namespace MWWorld
return TimeStamp(mGameHour, mDaysPassed); return TimeStamp(mGameHour, mDaysPassed);
} }
float DateTimeManager::getTimeScaleFactor() const void DateTimeManager::setGameTimeScale(float scale)
{ {
return mTimeScale; MWBase::Environment::get().getWorld()->setGlobalFloat(MWWorld::Globals::sTimeScale, scale);
} }
ESM::EpochTimeStamp DateTimeManager::getEpochTimeStamp() const ESM::EpochTimeStamp DateTimeManager::getEpochTimeStamp() const
@ -199,7 +205,7 @@ namespace MWWorld
} }
else if (name == Globals::sTimeScale) else if (name == Globals::sTimeScale)
{ {
mTimeScale = value; mGameTimeScale = value;
} }
else if (name == Globals::sDaysPassed) else if (name == Globals::sDaysPassed)
{ {
@ -232,7 +238,7 @@ namespace MWWorld
} }
else if (name == Globals::sTimeScale) else if (name == Globals::sTimeScale)
{ {
mTimeScale = static_cast<float>(value); mGameTimeScale = static_cast<float>(value);
} }
else if (name == Globals::sDaysPassed) else if (name == Globals::sDaysPassed)
{ {
@ -241,4 +247,22 @@ namespace MWWorld
return false; return false;
} }
void DateTimeManager::setSimulationTimeScale(float scale)
{
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();
}
} }

View File

@ -1,6 +1,7 @@
#ifndef GAME_MWWORLD_DATETIMEMANAGER_H #ifndef GAME_MWWORLD_DATETIMEMANAGER_H
#define GAME_MWWORLD_DATETIMEMANAGER_H #define GAME_MWWORLD_DATETIMEMANAGER_H
#include <set>
#include <string_view> #include <string_view>
#include "globalvariablename.hpp" #include "globalvariablename.hpp"
@ -14,31 +15,58 @@ namespace MWWorld
{ {
class Globals; class Globals;
class TimeStamp; class TimeStamp;
class World;
class DateTimeManager class DateTimeManager
{ {
int mDaysPassed = 0; public:
int mDay = 0; // Game time.
int mMonth = 0; // Note that game time generally goes faster than the simulation time.
int mYear = 0; std::string_view getMonthName(int month = -1) const; // -1: current month
float mGameHour = 0.f; TimeStamp getTimeStamp() const;
float mTimeScale = 0.f; ESM::EpochTimeStamp getEpochTimeStamp() const;
double getGameTime() const { return (static_cast<double>(mDaysPassed) * 24 + mGameHour) * 3600.0; }
float getGameTimeScale() const { return mGameTimeScale; }
void setGameTimeScale(float scale); // game time to simulation time ratio
// Simulation time (the number of seconds passed from the beginning of the game).
double getSimulationTime() const { return mSimulationTime; }
void setSimulationTime(double t) { mSimulationTime = t; }
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<std::string, std::less<>>& getPausedTags() const { return mPausedTags; }
// Updates mPaused; should be called once a frame.
void updateIsPaused();
private:
friend class World;
void setup(Globals& globalVariables);
bool updateGlobalInt(GlobalVariableName name, int value);
bool updateGlobalFloat(GlobalVariableName name, float value);
void advanceTime(double hours, Globals& globalVariables);
void setHour(double hour); void setHour(double hour);
void setDay(int day); void setDay(int day);
void setMonth(int month); void setMonth(int month);
public: int mDaysPassed = 0;
std::string_view getMonthName(int month) const; int mDay = 0;
TimeStamp getTimeStamp() const; int mMonth = 0;
ESM::EpochTimeStamp getEpochTimeStamp() const; int mYear = 0;
float getTimeScaleFactor() const; float mGameHour = 0.f;
float mGameTimeScale = 0.f;
void advanceTime(double hours, Globals& globalVariables); float mSimulationTimeScale = 1.0;
double mSimulationTime = 0.0;
void setup(Globals& globalVariables); bool mPaused = false;
bool updateGlobalInt(GlobalVariableName name, int value); std::set<std::string, std::less<>> mPausedTags;
bool updateGlobalFloat(GlobalVariableName name, float value);
}; };
} }

View File

@ -249,7 +249,7 @@ namespace MWWorld
: mResourceSystem(resourceSystem) : mResourceSystem(resourceSystem)
, mLocalScripts(mStore) , mLocalScripts(mStore)
, mWorldModel(mStore, mReaders) , mWorldModel(mStore, mReaders)
, mCurrentDate(std::make_unique<DateTimeManager>()) , mTimeManager(std::make_unique<DateTimeManager>())
, mSky(true) , mSky(true)
, mGodMode(false) , mGodMode(false)
, mScriptsEnabled(true) , mScriptsEnabled(true)
@ -319,7 +319,7 @@ namespace MWWorld
void World::fillGlobalVariables() void World::fillGlobalVariables()
{ {
mGlobalVariables.fill(mStore); mGlobalVariables.fill(mStore);
mCurrentDate->setup(mGlobalVariables); mTimeManager->setup(mGlobalVariables);
} }
void World::startNewGame(bool bypass) void World::startNewGame(bool bypass)
@ -399,7 +399,7 @@ namespace MWWorld
mPhysics->toggleCollisionMode(); mPhysics->toggleCollisionMode();
MWBase::Environment::get().getWindowManager()->updatePlayer(); MWBase::Environment::get().getWindowManager()->updatePlayer();
mCurrentDate->setup(mGlobalVariables); mTimeManager->setup(mGlobalVariables);
// Initial seed. // Initial seed.
mPrng.seed(mRandomSeed); mPrng.seed(mRandomSeed);
@ -615,7 +615,7 @@ namespace MWWorld
void World::setGlobalInt(GlobalVariableName name, int value) void World::setGlobalInt(GlobalVariableName name, int value)
{ {
bool dateUpdated = mCurrentDate->updateGlobalInt(name, value); bool dateUpdated = mTimeManager->updateGlobalInt(name, value);
if (dateUpdated) if (dateUpdated)
updateSkyDate(); updateSkyDate();
@ -624,7 +624,7 @@ namespace MWWorld
void World::setGlobalFloat(GlobalVariableName name, float value) void World::setGlobalFloat(GlobalVariableName name, float value)
{ {
bool dateUpdated = mCurrentDate->updateGlobalFloat(name, value); bool dateUpdated = mTimeManager->updateGlobalFloat(name, value);
if (dateUpdated) if (dateUpdated)
updateSkyDate(); updateSkyDate();
@ -646,11 +646,6 @@ namespace MWWorld
return mGlobalVariables.getType(name); return mGlobalVariables.getType(name);
} }
std::string_view World::getMonthName(int month) const
{
return mCurrentDate->getMonthName(month);
}
std::string_view World::getCellName(const MWWorld::CellStore* cell) const std::string_view World::getCellName(const MWWorld::CellStore* cell) const
{ {
if (!cell) if (!cell)
@ -893,7 +888,7 @@ namespace MWWorld
// When we fast-forward time, we should recharge magic items // When we fast-forward time, we should recharge magic items
// in all loaded cells, using game world time // in all loaded cells, using game world time
float duration = hours * 3600; float duration = hours * 3600;
const float timeScaleFactor = getTimeScaleFactor(); const float timeScaleFactor = mTimeManager->getGameTimeScale();
if (timeScaleFactor != 0.0f) if (timeScaleFactor != 0.0f)
duration /= timeScaleFactor; duration /= timeScaleFactor;
@ -901,7 +896,7 @@ namespace MWWorld
} }
mWeatherManager->advanceTime(hours, incremental); mWeatherManager->advanceTime(hours, incremental);
mCurrentDate->advanceTime(hours, mGlobalVariables); mTimeManager->advanceTime(hours, mGlobalVariables);
updateSkyDate(); updateSkyDate();
if (!incremental) if (!incremental)
@ -912,25 +907,9 @@ namespace MWWorld
} }
} }
float World::getTimeScaleFactor() const
{
return mCurrentDate->getTimeScaleFactor();
}
void World::setSimulationTimeScale(float scale)
{
mSimulationTimeScale = std::max(0.f, scale);
MWBase::Environment::get().getSoundManager()->setSimulationTimeScale(mSimulationTimeScale);
}
TimeStamp World::getTimeStamp() const TimeStamp World::getTimeStamp() const
{ {
return mCurrentDate->getTimeStamp(); return mTimeManager->getTimeStamp();
}
ESM::EpochTimeStamp World::getEpochTimeStamp() const
{
return mCurrentDate->getEpochTimeStamp();
} }
bool World::toggleSky() bool World::toggleSky()
@ -2314,7 +2293,7 @@ namespace MWWorld
{ {
mStore.rebuildIdsIndex(); mStore.rebuildIdsIndex();
mStore.validateDynamic(); mStore.validateDynamic();
mCurrentDate->setup(mGlobalVariables); mTimeManager->setup(mGlobalVariables);
} }
void World::setupPlayer() void World::setupPlayer()
@ -3864,7 +3843,7 @@ namespace MWWorld
void World::updateSkyDate() void World::updateSkyDate()
{ {
ESM::EpochTimeStamp currentDate = mCurrentDate->getEpochTimeStamp(); ESM::EpochTimeStamp currentDate = mTimeManager->getEpochTimeStamp();
mRendering->skySetDate(currentDate.mDay, currentDate.mMonth); mRendering->skySetDate(currentDate.mDay, currentDate.mMonth);
} }

View File

@ -102,7 +102,7 @@ namespace MWWorld
std::unique_ptr<MWRender::RenderingManager> mRendering; std::unique_ptr<MWRender::RenderingManager> mRendering;
std::unique_ptr<MWWorld::Scene> mWorldScene; std::unique_ptr<MWWorld::Scene> mWorldScene;
std::unique_ptr<MWWorld::WeatherManager> mWeatherManager; std::unique_ptr<MWWorld::WeatherManager> mWeatherManager;
std::unique_ptr<MWWorld::DateTimeManager> mCurrentDate; std::unique_ptr<MWWorld::DateTimeManager> mTimeManager;
std::unique_ptr<ProjectileManager> mProjectileManager; std::unique_ptr<ProjectileManager> mProjectileManager;
bool mSky; bool mSky;
@ -135,8 +135,6 @@ namespace MWWorld
uint32_t mRandomSeed{}; uint32_t mRandomSeed{};
float mSimulationTimeScale = 1.0;
// not implemented // not implemented
World(const World&); World(const World&);
World& operator=(const World&); World& operator=(const World&);
@ -309,15 +307,9 @@ namespace MWWorld
void advanceTime(double hours, bool incremental = false) override; void advanceTime(double hours, bool incremental = false) override;
///< Advance in-game time. ///< Advance in-game time.
std::string_view getMonthName(int month = -1) const override;
///< Return name of month (-1: current month)
TimeStamp getTimeStamp() const override; TimeStamp getTimeStamp() const override;
///< Return current in-game time and number of day since new game start. ///< Return current in-game time and number of day since new game start.
ESM::EpochTimeStamp getEpochTimeStamp() const override;
///< Return current in-game date and time.
bool toggleSky() override; bool toggleSky() override;
///< \return Resulting mode ///< \return Resulting mode
@ -339,12 +331,6 @@ namespace MWWorld
void modRegion(const ESM::RefId& regionid, const std::vector<char>& chances) override; void modRegion(const ESM::RefId& regionid, const std::vector<char>& chances) override;
float getTimeScaleFactor() const override;
float getSimulationTimeScale() const override { return mSimulationTimeScale; }
void setSimulationTimeScale(float scale) override;
void changeToInteriorCell(const std::string_view cellName, const ESM::Position& position, bool adjustPlayerPos, void changeToInteriorCell(const std::string_view cellName, const ESM::Position& position, bool adjustPlayerPos,
bool changeEvent = true) override; bool changeEvent = true) override;
///< Move to interior cell. ///< Move to interior cell.
@ -690,6 +676,8 @@ namespace MWWorld
MWRender::PostProcessor* getPostProcessor() override; MWRender::PostProcessor* getPostProcessor() override;
DateTimeManager* getTimeManager() override { return mTimeManager.get(); }
void setActorActive(const MWWorld::Ptr& ptr, bool value) override; void setActorActive(const MWWorld::Ptr& ptr, bool value) override;
}; };
} }

View File

@ -13,7 +13,7 @@
// //
// Subrecords: // Subrecords:
// LUAF - LuaScriptCfg::mFlags and ESM::RecNameInts list // LUAF - LuaScriptCfg::mFlags and ESM::RecNameInts list
// LUAW - Start of MWLua::WorldView data // LUAW - Simulation time and last generated RefNum
// LUAE - Start of MWLua::LocalEvent or MWLua::GlobalEvent (eventName) // LUAE - Start of MWLua::LocalEvent or MWLua::GlobalEvent (eventName)
// LUAS - VFS path to a Lua script // LUAS - VFS path to a Lua script
// LUAD - Serialized Lua variable // LUAD - Serialized Lua variable

View File

@ -106,6 +106,21 @@
-- @function [parent=#world] isWorldPaused -- @function [parent=#world] isWorldPaused
-- @return #boolean -- @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. -- Return an object by RefNum/FormId.
-- Note: the function always returns @{openmw.core#GameObject} and doesn't validate that -- Note: the function always returns @{openmw.core#GameObject} and doesn't validate that