summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPetr Mikheev <ptmikheev@gmail.com>2021-12-01 21:28:05 +0100
committerPetr Mikheev <ptmikheev@gmail.com>2022-01-10 21:03:35 +0100
commit2d1b1002397f7e7394b18d9e35a658e2f5bcb4ee (patch)
treed821686711836633a7f97e8dff3a6cf25a687ae6
parent8ee8f81619f294cabe70c21bd363fefcaf1f5c87 (diff)
Change terminology of gameSecond/gameHour to simulationTime/gameTime
-rw-r--r--apps/openmw/mwlua/asyncbindings.cpp22
-rw-r--r--apps/openmw/mwlua/luabindings.cpp28
-rw-r--r--apps/openmw/mwlua/luamanagerimp.cpp10
-rw-r--r--apps/openmw/mwlua/worldview.cpp8
-rw-r--r--apps/openmw/mwlua/worldview.hpp17
-rw-r--r--apps/openmw_test_suite/lua/test_scriptscontainer.cpp26
-rw-r--r--components/esm/luascripts.cpp4
-rw-r--r--components/esm/luascripts.hpp8
-rw-r--r--components/lua/luastate.cpp7
-rw-r--r--components/lua/scriptscontainer.cpp40
-rw-r--r--components/lua/scriptscontainer.hpp19
-rw-r--r--docs/source/reference/lua-scripting/api.rst3
-rw-r--r--docs/source/reference/lua-scripting/openmw_aux_time.rst5
-rw-r--r--docs/source/reference/lua-scripting/overview.rst9
-rw-r--r--files/builtin_scripts/openmw_aux/time.lua104
-rw-r--r--files/builtin_scripts/openmw_aux/util.lua67
-rw-r--r--files/lua_api/openmw/async.lua16
-rw-r--r--files/lua_api/openmw/core.lua19
-rw-r--r--files/lua_api/openmw/world.lua30
-rw-r--r--files/lua_api/os.doclua64
20 files changed, 342 insertions, 164 deletions
diff --git a/apps/openmw/mwlua/asyncbindings.cpp b/apps/openmw/mwlua/asyncbindings.cpp
index 9bddf75ee4..0ffb2aad8f 100644
--- a/apps/openmw/mwlua/asyncbindings.cpp
+++ b/apps/openmw/mwlua/asyncbindings.cpp
@@ -19,36 +19,36 @@ namespace MWLua
sol::function getAsyncPackageInitializer(const Context& context)
{
- using TimeUnit = LuaUtil::ScriptsContainer::TimeUnit;
+ using TimerType = LuaUtil::ScriptsContainer::TimerType;
sol::usertype<AsyncPackageId> api = context.mLua->sol().new_usertype<AsyncPackageId>("AsyncPackage");
api["registerTimerCallback"] = [](const AsyncPackageId& asyncId, std::string_view name, sol::function callback)
{
asyncId.mContainer->registerTimerCallback(asyncId.mScriptId, name, std::move(callback));
return TimerCallback{asyncId, std::string(name)};
};
- api["newTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
- const TimerCallback& callback, sol::object callbackArg)
+ api["newSimulationTimer"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
+ const TimerCallback& callback, sol::object callbackArg)
{
callback.mAsyncId.mContainer->setupSerializableTimer(
- TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay,
+ TimerType::SIMULATION_TIME, world->getSimulationTime() + delay,
callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg));
};
- api["newTimerInHours"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
- const TimerCallback& callback, sol::object callbackArg)
+ api["newGameTimer"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
+ const TimerCallback& callback, sol::object callbackArg)
{
callback.mAsyncId.mContainer->setupSerializableTimer(
- TimeUnit::HOURS, world->getGameTimeInHours() + delay,
+ TimerType::GAME_TIME, world->getGameTime() + delay,
callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg));
};
- api["newUnsavableTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
+ api["newUnsavableSimulationTimer"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
{
asyncId.mContainer->setupUnsavableTimer(
- TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay, asyncId.mScriptId, std::move(callback));
+ TimerType::SIMULATION_TIME, world->getSimulationTime() + delay, asyncId.mScriptId, std::move(callback));
};
- api["newUnsavableTimerInHours"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
+ api["newUnsavableGameTimer"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
{
asyncId.mContainer->setupUnsavableTimer(
- TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScriptId, std::move(callback));
+ TimerType::GAME_TIME, world->getGameTime() + delay, asyncId.mScriptId, std::move(callback));
};
api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn)
{
diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp
index c525fd8a23..5a9e33faef 100644
--- a/apps/openmw/mwlua/luabindings.cpp
+++ b/apps/openmw/mwlua/luabindings.cpp
@@ -22,11 +22,32 @@ namespace MWLua
return LuaUtil::makeReadOnly(res);
}
+ static void addTimeBindings(sol::table& api, const Context& context, bool global)
+ {
+ api["getSimulationTime"] = [world=context.mWorldView]() { return world->getSimulationTime(); };
+ api["getSimulationTimeScale"] = [world=context.mWorldView]() { return world->getSimulationTimeScale(); };
+ api["getGameTime"] = [world=context.mWorldView]() { return world->getGameTime(); };
+ api["getGameTimeScale"] = [world=context.mWorldView]() { return world->getGameTimeScale(); };
+ api["isWorldPaused"] = [world=context.mWorldView]() { return world->isPaused(); };
+
+ if (!global)
+ return;
+
+ api["setGameTimeScale"] = [world=context.mWorldView](double scale) { world->setGameTimeScale(scale); };
+
+ // TODO: Ability to make game time slower or faster than real time (needed for example for mechanics like VATS)
+ // api["setSimulationTimeScale"] = [](double scale) {};
+
+ // TODO: Ability to pause/resume world from Lua (needed for UI dehardcoding)
+ // api["pause"] = []() {};
+ // api["resume"] = []() {};
+ }
+
sol::table initCorePackage(const Context& context)
{
auto* lua = context.mLua;
sol::table api(lua->sol(), sol::create);
- api["API_REVISION"] = 12;
+ api["API_REVISION"] = 13;
api["quit"] = [lua]()
{
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
@@ -36,9 +57,7 @@ namespace MWLua
{
context.mGlobalEventQueue->push_back({std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer)});
};
- api["getGameTimeInSeconds"] = [world=context.mWorldView]() { return world->getGameTimeInSeconds(); };
- api["getGameTimeInHours"] = [world=context.mWorldView]() { return world->getGameTimeInHours(); };
- api["isWorldPaused"] = [world=context.mWorldView]() { return world->isPaused(); };
+ addTimeBindings(api, context, false);
api["OBJECT_TYPE"] = definitionList(*lua,
{
"Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient",
@@ -73,6 +92,7 @@ namespace MWLua
{
sol::table api(context.mLua->sol(), sol::create);
WorldView* worldView = context.mWorldView;
+ addTimeBindings(api, context, true);
api["getCellByName"] = [worldView=context.mWorldView](const std::string& name) -> sol::optional<GCell>
{
MWWorld::CellStore* cell = worldView->findNamedCell(name);
diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp
index be5764c32f..658ee7c809 100644
--- a/apps/openmw/mwlua/luamanagerimp.cpp
+++ b/apps/openmw/mwlua/luamanagerimp.cpp
@@ -113,13 +113,13 @@ namespace MWLua
if (!mWorldView.isPaused())
{ // Update time and process timers
- double seconds = mWorldView.getGameTimeInSeconds() + frameDuration;
- mWorldView.setGameTimeInSeconds(seconds);
- double hours = mWorldView.getGameTimeInHours();
+ double simulationTime = mWorldView.getSimulationTime() + frameDuration;
+ mWorldView.setSimulationTime(simulationTime);
+ double gameTime = mWorldView.getGameTime();
- mGlobalScripts.processTimers(seconds, hours);
+ mGlobalScripts.processTimers(simulationTime, gameTime);
for (LocalScripts* scripts : mActiveLocalScripts)
- scripts->processTimers(seconds, hours);
+ scripts->processTimers(simulationTime, gameTime);
}
// Receive events
diff --git a/apps/openmw/mwlua/worldview.cpp b/apps/openmw/mwlua/worldview.cpp
index 219035745a..35b1db7a93 100644
--- a/apps/openmw/mwlua/worldview.cpp
+++ b/apps/openmw/mwlua/worldview.cpp
@@ -70,16 +70,16 @@ namespace MWLua
removeFromGroup(*group, ptr);
}
- double WorldView::getGameTimeInHours() const
+ double WorldView::getGameTime() const
{
MWBase::World* world = MWBase::Environment::get().getWorld();
MWWorld::TimeStamp timeStamp = world->getTimeStamp();
- return static_cast<double>(timeStamp.getDay()) * 24 + timeStamp.getHour();
+ return (static_cast<double>(timeStamp.getDay()) * 24 + timeStamp.getHour()) * 3600.0;
}
void WorldView::load(ESM::ESMReader& esm)
{
- esm.getHNT(mGameSeconds, "LUAW");
+ esm.getHNT(mSimulationTime, "LUAW");
ObjectId lastAssignedId;
lastAssignedId.load(esm, true);
mObjectRegistry.setLastAssignedId(lastAssignedId);
@@ -87,7 +87,7 @@ namespace MWLua
void WorldView::save(ESM::ESMWriter& esm) const
{
- esm.writeHNT("LUAW", mGameSeconds);
+ esm.writeHNT("LUAW", mSimulationTime);
mObjectRegistry.getLastAssignedId().save(esm, true);
}
diff --git a/apps/openmw/mwlua/worldview.hpp b/apps/openmw/mwlua/worldview.hpp
index a8befd4685..e5b5fe4f67 100644
--- a/apps/openmw/mwlua/worldview.hpp
+++ b/apps/openmw/mwlua/worldview.hpp
@@ -22,13 +22,16 @@ namespace MWLua
// Whether the world is paused (i.e. game time is not changing and actors don't move).
bool isPaused() const { return mPaused; }
- // Returns the number of seconds passed from the beginning of the game.
- double getGameTimeInSeconds() const { return mGameSeconds; }
- void setGameTimeInSeconds(double t) { mGameSeconds = t; }
+ // The number of seconds passed from the beginning of the game.
+ double getSimulationTime() const { return mSimulationTime; }
+ void setSimulationTime(double t) { mSimulationTime = t; }
+ double getSimulationTimeScale() const { return 1.0; }
- // Returns the number of game hours passed from the beginning of the game.
- // Note that the number of seconds in a game hour is not fixed.
- double getGameTimeInHours() const;
+ // 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("timescale", s); }
ObjectIdList getActivatorsInScene() const { return mActivatorsInScene.mList; }
ObjectIdList getActorsInScene() const { return mActorsInScene.mList; }
@@ -76,7 +79,7 @@ namespace MWLua
ObjectGroup mDoorsInScene;
ObjectGroup mItemsInScene;
- double mGameSeconds = 0;
+ double mSimulationTime = 0;
bool mPaused = false;
};
diff --git a/apps/openmw_test_suite/lua/test_scriptscontainer.cpp b/apps/openmw_test_suite/lua/test_scriptscontainer.cpp
index 8be02a2f13..779debb4e6 100644
--- a/apps/openmw_test_suite/lua/test_scriptscontainer.cpp
+++ b/apps/openmw_test_suite/lua/test_scriptscontainer.cpp
@@ -364,7 +364,7 @@ return {
TEST_F(LuaScriptsContainerTest, Timers)
{
- using TimeUnit = LuaUtil::ScriptsContainer::TimeUnit;
+ using TimerType = LuaUtil::ScriptsContainer::TimerType;
LuaUtil::ScriptsContainer scripts(&mLua, "Test");
int test1Id = *mCfg.findId("test1.lua");
int test2Id = *mCfg.findId("test2.lua");
@@ -387,18 +387,18 @@ return {
scripts.processTimers(1, 2);
- scripts.setupSerializableTimer(TimeUnit::SECONDS, 10, test1Id, "B", sol::make_object(mLua.sol(), 3));
- scripts.setupSerializableTimer(TimeUnit::HOURS, 10, test2Id, "B", sol::make_object(mLua.sol(), 4));
- scripts.setupSerializableTimer(TimeUnit::SECONDS, 5, test1Id, "A", sol::make_object(mLua.sol(), 1));
- scripts.setupSerializableTimer(TimeUnit::HOURS, 5, test2Id, "A", sol::make_object(mLua.sol(), 2));
- scripts.setupSerializableTimer(TimeUnit::SECONDS, 15, test1Id, "A", sol::make_object(mLua.sol(), 10));
- scripts.setupSerializableTimer(TimeUnit::SECONDS, 15, test1Id, "B", sol::make_object(mLua.sol(), 20));
-
- scripts.setupUnsavableTimer(TimeUnit::SECONDS, 10, test2Id, fn2);
- scripts.setupUnsavableTimer(TimeUnit::HOURS, 10, test1Id, fn2);
- scripts.setupUnsavableTimer(TimeUnit::SECONDS, 5, test2Id, fn1);
- scripts.setupUnsavableTimer(TimeUnit::HOURS, 5, test1Id, fn1);
- scripts.setupUnsavableTimer(TimeUnit::SECONDS, 15, test2Id, fn1);
+ scripts.setupSerializableTimer(TimerType::SIMULATION_TIME, 10, test1Id, "B", sol::make_object(mLua.sol(), 3));
+ scripts.setupSerializableTimer(TimerType::GAME_TIME, 10, test2Id, "B", sol::make_object(mLua.sol(), 4));
+ scripts.setupSerializableTimer(TimerType::SIMULATION_TIME, 5, test1Id, "A", sol::make_object(mLua.sol(), 1));
+ scripts.setupSerializableTimer(TimerType::GAME_TIME, 5, test2Id, "A", sol::make_object(mLua.sol(), 2));
+ scripts.setupSerializableTimer(TimerType::SIMULATION_TIME, 15, test1Id, "A", sol::make_object(mLua.sol(), 10));
+ scripts.setupSerializableTimer(TimerType::SIMULATION_TIME, 15, test1Id, "B", sol::make_object(mLua.sol(), 20));
+
+ scripts.setupUnsavableTimer(TimerType::SIMULATION_TIME, 10, test2Id, fn2);
+ scripts.setupUnsavableTimer(TimerType::GAME_TIME, 10, test1Id, fn2);
+ scripts.setupUnsavableTimer(TimerType::SIMULATION_TIME, 5, test2Id, fn1);
+ scripts.setupUnsavableTimer(TimerType::GAME_TIME, 5, test1Id, fn1);
+ scripts.setupUnsavableTimer(TimerType::SIMULATION_TIME, 15, test2Id, fn1);
EXPECT_EQ(counter1, 0);
EXPECT_EQ(counter3, 0);
diff --git a/components/esm/luascripts.cpp b/components/esm/luascripts.cpp
index c831cbbbfc..53beb02d82 100644
--- a/components/esm/luascripts.cpp
+++ b/components/esm/luascripts.cpp
@@ -72,7 +72,7 @@ void ESM::LuaScripts::load(ESMReader& esm)
{
esm.getSubHeader();
LuaTimer timer;
- esm.getT(timer.mUnit);
+ esm.getT(timer.mType);
esm.getT(timer.mTime);
timer.mCallbackName = esm.getHNString("LUAC");
timer.mCallbackArgument = loadLuaBinaryData(esm);
@@ -91,7 +91,7 @@ void ESM::LuaScripts::save(ESMWriter& esm) const
for (const LuaTimer& timer : script.mTimers)
{
esm.startSubRecord("LUAT");
- esm.writeT(timer.mUnit);
+ esm.writeT(timer.mType);
esm.writeT(timer.mTime);
esm.endRecord("LUAT");
esm.writeHNString("LUAC", timer.mCallbackName);
diff --git a/components/esm/luascripts.hpp b/components/esm/luascripts.hpp
index e6f7113c16..985d756fce 100644
--- a/components/esm/luascripts.hpp
+++ b/components/esm/luascripts.hpp
@@ -51,13 +51,13 @@ namespace ESM
struct LuaTimer
{
- enum class TimeUnit : bool
+ enum class Type : bool
{
- SECONDS = 0,
- HOURS = 1,
+ SIMULATION_TIME = 0,
+ GAME_TIME = 1,
};
- TimeUnit mUnit;
+ Type mType;
double mTime;
std::string mCallbackName;
std::string mCallbackArgument; // Serialized Lua table. It is a binary data. Can contain '\0'.
diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp
index 6ff698c9c2..bfe9cb513c 100644
--- a/components/lua/luastate.cpp
+++ b/components/lua/luastate.cpp
@@ -52,7 +52,7 @@ namespace LuaUtil
LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf) : mConf(conf), mVFS(vfs)
{
mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math,
- sol::lib::string, sol::lib::table, sol::lib::debug);
+ sol::lib::string, sol::lib::table, sol::lib::os, sol::lib::debug);
mLua["math"]["randomseed"](static_cast<unsigned>(std::time(nullptr)));
mLua["math"]["randomseed"] = []{};
@@ -85,6 +85,11 @@ namespace LuaUtil
if (mLua[s] == sol::nil) throw std::logic_error("Lua package not found: " + s);
mCommonPackages[s] = mSandboxEnv[s] = makeReadOnly(mLua[s]);
}
+ mCommonPackages["os"] = mSandboxEnv["os"] = makeReadOnly(tableFromPairs<std::string_view, sol::function>({
+ {"date", mLua["os"]["date"]},
+ {"difftime", mLua["os"]["difftime"]},
+ {"time", mLua["os"]["time"]}
+ }));
}
LuaState::~LuaState()
diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp
index eb9da7a60b..780d10cebd 100644
--- a/components/lua/scriptscontainer.cpp
+++ b/components/lua/scriptscontainer.cpp
@@ -309,21 +309,21 @@ namespace LuaUtil
void ScriptsContainer::save(ESM::LuaScripts& data)
{
std::map<int, std::vector<ESM::LuaTimer>> timers;
- auto saveTimerFn = [&](const Timer& timer, TimeUnit timeUnit)
+ auto saveTimerFn = [&](const Timer& timer, TimerType timerType)
{
if (!timer.mSerializable)
return;
ESM::LuaTimer savedTimer;
savedTimer.mTime = timer.mTime;
- savedTimer.mUnit = timeUnit;
+ savedTimer.mType = timerType;
savedTimer.mCallbackName = std::get<std::string>(timer.mCallback);
savedTimer.mCallbackArgument = timer.mSerializedArg;
timers[timer.mScriptId].push_back(std::move(savedTimer));
};
- for (const Timer& timer : mSecondsTimersQueue)
- saveTimerFn(timer, TimeUnit::SECONDS);
- for (const Timer& timer : mHoursTimersQueue)
- saveTimerFn(timer, TimeUnit::HOURS);
+ for (const Timer& timer : mSimulationTimersQueue)
+ saveTimerFn(timer, TimerType::SIMULATION_TIME);
+ for (const Timer& timer : mGameTimersQueue)
+ saveTimerFn(timer, TimerType::GAME_TIME);
data.mScripts.clear();
for (auto& [scriptId, script] : mScripts)
{
@@ -408,17 +408,17 @@ namespace LuaUtil
// updates refnums, so timer.mSerializedArg may be not equal to savedTimer.mCallbackArgument.
timer.mSerializedArg = serialize(timer.mArg, mSerializer);
- if (savedTimer.mUnit == TimeUnit::HOURS)
- mHoursTimersQueue.push_back(std::move(timer));
+ if (savedTimer.mType == TimerType::GAME_TIME)
+ mGameTimersQueue.push_back(std::move(timer));
else
- mSecondsTimersQueue.push_back(std::move(timer));
+ mSimulationTimersQueue.push_back(std::move(timer));
}
catch (std::exception& e) { printError(scriptId, "can not load timer", e); }
}
}
- std::make_heap(mSecondsTimersQueue.begin(), mSecondsTimersQueue.end());
- std::make_heap(mHoursTimersQueue.begin(), mHoursTimersQueue.end());
+ std::make_heap(mSimulationTimersQueue.begin(), mSimulationTimersQueue.end());
+ std::make_heap(mGameTimersQueue.begin(), mGameTimersQueue.end());
}
ScriptsContainer::~ScriptsContainer()
@@ -437,8 +437,8 @@ namespace LuaUtil
for (auto& [_, handlers] : mEngineHandlers)
handlers->mList.clear();
mEventHandlers.clear();
- mSecondsTimersQueue.clear();
- mHoursTimersQueue.clear();
+ mSimulationTimersQueue.clear();
+ mGameTimersQueue.clear();
mPublicInterfaces.clear();
// Assigned by LuaUtil::makeReadOnly, but `clear` removes it, so we need to assign it again.
@@ -464,7 +464,7 @@ namespace LuaUtil
std::push_heap(timerQueue.begin(), timerQueue.end());
}
- void ScriptsContainer::setupSerializableTimer(TimeUnit timeUnit, double time, int scriptId,
+ void ScriptsContainer::setupSerializableTimer(TimerType type, double time, int scriptId,
std::string_view callbackName, sol::object callbackArg)
{
Timer t;
@@ -474,10 +474,10 @@ namespace LuaUtil
t.mTime = time;
t.mArg = callbackArg;
t.mSerializedArg = serialize(t.mArg, mSerializer);
- insertTimer(timeUnit == TimeUnit::HOURS ? mHoursTimersQueue : mSecondsTimersQueue, std::move(t));
+ insertTimer(type == TimerType::GAME_TIME ? mGameTimersQueue : mSimulationTimersQueue, std::move(t));
}
- void ScriptsContainer::setupUnsavableTimer(TimeUnit timeUnit, double time, int scriptId, sol::function callback)
+ void ScriptsContainer::setupUnsavableTimer(TimerType type, double time, int scriptId, sol::function callback)
{
Timer t;
t.mScriptId = scriptId;
@@ -488,7 +488,7 @@ namespace LuaUtil
getScript(t.mScriptId).mTemporaryCallbacks.emplace(mTemporaryCallbackCounter, std::move(callback));
mTemporaryCallbackCounter++;
- insertTimer(timeUnit == TimeUnit::HOURS ? mHoursTimersQueue : mSecondsTimersQueue, std::move(t));
+ insertTimer(type == TimerType::GAME_TIME ? mGameTimersQueue : mSimulationTimersQueue, std::move(t));
}
void ScriptsContainer::callTimer(const Timer& t)
@@ -524,10 +524,10 @@ namespace LuaUtil
}
}
- void ScriptsContainer::processTimers(double gameSeconds, double gameHours)
+ void ScriptsContainer::processTimers(double simulationTime, double gameTime)
{
- updateTimerQueue(mSecondsTimersQueue, gameSeconds);
- updateTimerQueue(mHoursTimersQueue, gameHours);
+ updateTimerQueue(mSimulationTimersQueue, simulationTime);
+ updateTimerQueue(mGameTimersQueue, gameTime);
}
}
diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp
index 1863d04669..fcbd2ba0b7 100644
--- a/components/lua/scriptscontainer.hpp
+++ b/components/lua/scriptscontainer.hpp
@@ -73,7 +73,7 @@ namespace LuaUtil
ScriptsContainer* mContainer;
int mIndex; // index in LuaUtil::ScriptsConfiguration
};
- using TimeUnit = ESM::LuaTimer::TimeUnit;
+ using TimerType = ESM::LuaTimer::Type;
// `namePrefix` is a common prefix for all scripts in the container. Used in logs for error messages and `print` output.
// `autoStartMode` specifies the list of scripts that should be autostarted in this container; the list itself is
@@ -99,8 +99,7 @@ namespace LuaUtil
bool hasScript(int scriptId) const { return mScripts.count(scriptId) != 0; }
void removeScript(int scriptId);
- // Processes timers. gameSeconds and gameHours are time (in seconds and in game hours) passed from the game start.
- void processTimers(double gameSeconds, double gameHours);
+ void processTimers(double simulationTime, double gameTime);
// Calls `onUpdate` (if present) for every script in the container.
// Handlers are called in the same order as scripts were added.
@@ -134,17 +133,17 @@ namespace LuaUtil
void registerTimerCallback(int scriptId, std::string_view callbackName, sol::function callback);
// Sets up a timer, that can be automatically saved and loaded.
- // timeUnit - game seconds (TimeUnit::Seconds) or game hours (TimeUnit::Hours).
+ // type - the type of timer, either SIMULATION_TIME or GAME_TIME.
// time - the absolute game time (in seconds or in hours) when the timer should be executed.
// scriptPath - script path in VFS is used as script id. The script with the given path should already present in the container.
// callbackName - callback (should be registered in advance) for this timer.
// callbackArg - parameter for the callback (should be serializable).
- void setupSerializableTimer(TimeUnit timeUnit, double time, int scriptId,
+ void setupSerializableTimer(TimerType type, double time, int scriptId,
std::string_view callbackName, sol::object callbackArg);
- // Creates a timer. `callback` is an arbitrary Lua function. This type of timers is called "unsavable"
- // because it can not be stored in saves. I.e. loading a saved game will not fully restore the state.
- void setupUnsavableTimer(TimeUnit timeUnit, double time, int scriptId, sol::function callback);
+ // Creates a timer. `callback` is an arbitrary Lua function. These timers are called "unsavable"
+ // because they can not be stored in saves. I.e. loading a saved game will not fully restore the state.
+ void setupUnsavableTimer(TimerType type, double time, int scriptId, sol::function callback);
protected:
struct Handler
@@ -237,8 +236,8 @@ namespace LuaUtil
std::map<std::string_view, EngineHandlerList*> mEngineHandlers;
std::map<std::string, EventHandlerList, std::less<>> mEventHandlers;
- std::vector<Timer> mSecondsTimersQueue;
- std::vector<Timer> mHoursTimersQueue;
+ std::vector<Timer> mSimulationTimersQueue;
+ std::vector<Timer> mGameTimersQueue;
int64_t mTemporaryCallbackCounter = 0;
};
diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst
index dd9d151482..635fcfb95e 100644
--- a/docs/source/reference/lua-scripting/api.rst
+++ b/docs/source/reference/lua-scripting/api.rst
@@ -19,6 +19,7 @@ Lua API reference
openmw_ui
openmw_camera
openmw_aux_util
+ openmw_aux_time
interface_camera
@@ -75,6 +76,8 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid
+=========================================================+====================+===============================================================+
|:ref:`openmw_aux.util <Package openmw_aux.util>` | everywhere | | Miscellaneous utils |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
+|:ref:`openmw_aux.time <Package openmw_aux.time>` | everywhere | | Timers and game time utils |
++---------------------------------------------------------+--------------------+---------------------------------------------------------------+
**Interfaces of built-in scripts**
diff --git a/docs/source/reference/lua-scripting/openmw_aux_time.rst b/docs/source/reference/lua-scripting/openmw_aux_time.rst
new file mode 100644
index 0000000000..120d888a01
--- /dev/null
+++ b/docs/source/reference/lua-scripting/openmw_aux_time.rst
@@ -0,0 +1,5 @@
+Package openmw_aux.time
+=======================
+
+.. raw:: html
+ :file: generated_html/openmw_aux_time.html
diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst
index 938611635a..103a93c559 100644
--- a/docs/source/reference/lua-scripting/overview.rst
+++ b/docs/source/reference/lua-scripting/overview.rst
@@ -15,10 +15,11 @@ Here are starting points for learning Lua:
Each script works in a separate sandbox and doesn't have any access to the underlying operating system.
Only a limited list of allowed standard libraries can be used:
`coroutine <https://www.lua.org/manual/5.1/manual.html#5.2>`__,
-`math <https://www.lua.org/manual/5.1/manual.html#5.6>`__,
+`math <https://www.lua.org/manual/5.1/manual.html#5.6>`__ (except `math.randomseed` -- it is called by the engine on startup and not available from scripts),
`string <https://www.lua.org/manual/5.1/manual.html#5.4>`__,
-`table <https://www.lua.org/manual/5.1/manual.html#5.5>`__.
-These libraries are loaded automatically and are always available (except the function `math.randomseed` -- it is called by the engine on startup and not available from scripts).
+`table <https://www.lua.org/manual/5.1/manual.html#5.5>`__,
+`os <https://www.lua.org/manual/5.1/manual.html#5.8>`__ (only `os.date`, `os.difftime`, `os.time`).
+These libraries are loaded automatically and are always available.
Allowed `basic functions <https://www.lua.org/manual/5.1/manual.html#5.1>`__:
``assert``, ``error``, ``ipairs``, ``next``, ``pairs``, ``pcall``, ``print``, ``select``, ``tonumber``, ``tostring``, ``type``, ``unpack``, ``xpcall``, ``rawequal``, ``rawget``, ``rawset``, ``getmetatable``, ``setmetatable``.
@@ -366,6 +367,8 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid
+=========================================================+====================+===============================================================+
|:ref:`openmw_aux.util <Package openmw_aux.util>` | everywhere | | Miscellaneous utils |
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
+|:ref:`openmw_aux.time <Package openmw_aux.time>` | everywhere | | Timers and game time utils |
++---------------------------------------------------------+--------------------+---------------------------------------------------------------+
They can be loaded with ``require`` the same as API packages. For example:
diff --git a/files/builtin_scripts/openmw_aux/time.lua b/files/builtin_scripts/openmw_aux/time.lua
new file mode 100644
index 0000000000..a6aac0fa1a
--- /dev/null
+++ b/files/builtin_scripts/openmw_aux/time.lua
@@ -0,0 +1,104 @@
+---
+-- `openmw_aux.time` defines utility functions for timers.
+-- Implementation can be found in `resources/vfs/openmw_aux/time.lua`.
+-- @module time
+-- @usage local time = require('openmw_aux.time')
+
+local time = {
+ second = 1,
+ minute = 60,
+ hour = 3600,
+ day = 3600 * 24,
+ GameTime = 'GameTime',
+ SimulationTime = 'SimulationTime',
+}
+
+---
+-- Alias of async:registerTimerCallback ; register a function as a timer callback.
+-- @function [parent=#time] registerTimerCallback
+-- @param #string name
+-- @param #function func
+-- @return openmw.async#TimerCallback
+function time.registerTimerCallback(name, fn)
+ local async = require('openmw.async')
+ return async:registerTimerCallback(name, fn)
+end
+
+---
+-- Alias of async:newSimulationTimer ; call callback(arg) in `delay` game seconds.
+-- Callback must be registered in advance.
+-- @function [parent=#time] newGameTimer
+-- @param #number delay
+-- @param openmw.async#TimerCallback callback A callback returned by `registerTimerCallback`
+-- @param arg An argument for `callback`; can be `nil`.
+function time.newGameTimer(delay, callback, callbackArg)
+ local async = require('openmw.async')
+ return async:newGameTimer(delay, callback, callbackArg)
+end
+
+---
+-- Alias of async:newSimulationTimer ; call callback(arg) in `delay` simulation seconds.
+-- Callback must be registered in advance.
+-- @function [parent=#time] newSimulationTimer
+-- @param #number delay
+-- @param openmw.async#TimerCallback callback A callback returned by `registerTimerCallback`
+-- @param arg An argument for `callback`; can be `nil`.
+function time.newSimulationTimer(delay, callback, callbackArg)
+ local async = require('openmw.async')
+ return async:newSimulationTimer(delay, callback, callbackArg)
+end
+
+---
+-- Run given function repeatedly.
+-- Note that loading a save stops the evaluation. If it should work always, call it during initialization of the script (i.e. not in a handler)
+-- @function [parent=#time] runRepeatedly
+-- @param #function fn the function that should be called
+-- @param #number period interval
+-- @param #table options additional options `initialDelay` and `type`.
+-- `initialDelay` - delay before the first call. If missed then the delay is a random number in range [0, N]. Randomization is used for performance reasons -- to prevent all scripts from doing time consuming operations at the same time.
+-- `type` - either `time.SimulationTime` (by default, timer uses simulation time) or `time.GameTime` (timer uses game time).
+-- @return #function a function without arguments that can be used to stop the periodical evaluation.
+-- @usage
+-- local stopFn = time.runRepeatedly(function() print('Test') end,
+-- 5 * time.second) -- print 'Test' every 5 seconds
+-- stopFn() -- stop printing 'Test'
+-- time.runRepeatedly( -- print 'Test' every 5 minutes with initial 30 second delay
+-- function() print('Test2') end, 5 * time.minute,
+-- { initialDelay = 30 * time.second })
+-- @usage
+-- local timeBeforeMidnight = time.day - time.gameTime() % time.day
+-- time.runRepeatedly(doSomething, time.day, {
+-- initialDelay = timeBeforeMidnight,
+-- type = time.GameTime,
+-- }) -- call `doSomething` at the end of every game day.
+function time.runRepeatedly(fn, period, options)
+ if period <= 0 then
+ error('Period must be positive. If you want it to be as small '..
+ 'as possible, use the engine handler `onUpdate` instead', 2)
+ end
+ local async = require('openmw.async')
+ local core = require('openmw.core')
+ local initialDelay = (options and options.initialDelay) or math.random() * period
+ local getTimeFn, newTimerFn
+ if (options and options.type) == time.GameTime then
+ getTimeFn = core.getGameTime
+ newTimerFn = async.newUnsavableGameTimer
+ else
+ getTimeFn = core.getSimulationTime
+ newTimerFn = async.newUnsavableSimulationTimer
+ end
+ local baseTime = getTimeFn() + initialDelay
+ local breakFlag = false
+ local wrappedFn
+ wrappedFn = function()
+ if breakFlag then return end
+ fn()
+ local nextDelay = 1.5 * period - math.fmod(getTimeFn() - baseTime + period / 2, period)
+ newTimerFn(async, nextDelay, wrappedFn)
+ end
+ newTimerFn(async, initialDelay, wrappedFn)
+ return function() breakFlag = true end
+end
+
+return time
+
diff --git a/files/builtin_scripts/openmw_aux/util.lua b/files/builtin_scripts/openmw_aux/util.lua
index ba52ced2bd..8d4f7c1fd7 100644
--- a/files/builtin_scripts/openmw_aux/util.lua
+++ b/files/builtin_scripts/openmw_aux/util.lua
@@ -28,72 +28,5 @@ function aux_util.findNearestTo(point, objectList)
return res, resDist
end
--------------------------------------------------------------------------------
--- Runs given function every N game seconds (seconds when the game is not paused).
--- Note that loading a save stops the evaluation. If it should work always, call it in 2 places --
--- when a script starts and in the engine handler `onLoad`.
--- @function [parent=#util] runEveryNSeconds
--- @param #number N interval in seconds
--- @param #function fn the function that should be called every N seconds
--- @param #number initialDelay optional argument -- delay in seconds before the first call. If missed then the delay is a random number in range [0, N]. Randomization is used for performance reasons -- to prevent all scripts from doing time consuming operations at the same time.
--- @return #function a function without arguments that can be used to stop the periodical evaluation.
--- @usage
--- local stopFn = aux_util.runEveryNSeconds(5, function() print('Test') end) -- print 'Test' every 5 seconds
--- stopFn() -- stop printing 'Test'
--- aux_util.runEveryNSeconds(5, function() print('Test2') end, 1) -- print 'Test' every 5 seconds starting from the next second
-function aux_util.runEveryNSeconds(N, fn, initialDelay)
- if N <= 0 then
- error('Interval must be positive. If you want it to be as small '..
- 'as possible, use the engine handler `onUpdate` instead', 2)
- end
- local async = require('openmw.async')
- local core = require('openmw.core')
- local breakFlag = false
- local initialDelay = initialDelay or math.random() * N
- local baseTime = core.getGameTimeInSeconds() + initialDelay
- local wrappedFn
- wrappedFn = function()
- if breakFlag then return end
- fn()
- local nextDelay = 1.5 * N - math.fmod(core.getGameTimeInSeconds() - baseTime + N / 2, N)
- async:newUnsavableTimerInSeconds(nextDelay, wrappedFn)
- end
- async:newUnsavableTimerInSeconds(initialDelay, wrappedFn)
- return function() breakFlag = true end
-end
-
--------------------------------------------------------------------------------
--- Runs given function every N game hours.
--- Note that loading a save stops the evaluation. If it should work always, call it in 2 places --
--- when a script starts and in the engine handler `onLoad`.
--- @function [parent=#util] runEveryNHours
--- @param #number N interval in game hours
--- @param #function fn the function that should be called every N game hours
--- @param #number initialDelay optional argument -- delay in game hours before the first call. If missed then the delay is a random number in range [0, N]. Randomization is used for performance reasons -- to prevent all scripts from doing time consuming operations at the same time.
--- @return #function a function without arguments that can be used to stop the periodical evaluation.
--- @usage
--- local timeBeforeMidnight = 24 - math.fmod(core.getGameTimeInHours(), 24)
--- aux_util.runEveryNHours(24, doSomething, timeBeforeMidnight) -- call `doSomething` at the end of every game day.
-function aux_util.runEveryNHours(N, fn, initialDelay)
- if N <= 0 then
- error('Interval must be positive. If you want it to be as small '..
- 'as possible, use the engine handler `onUpdate` instead', 2)
- end
- local async = require('openmw.async')
- local core = require('openmw.core')
- local breakFlag = false
- local initialDelay = initialDelay or math.random() * N
- local baseTime = core.getGameTimeInHours() + initialDelay
- local wrappedFn
- wrappedFn = function()
- if breakFlag then return end
- fn()
- local nextDelay = 1.5 * N - math.fmod(core.getGameTimeInHours() - baseTime + N / 2, N)
- async:newUnsavableTimerInHours(nextDelay, wrappedFn)
- end
- async:newUnsavableTimerInHours(initialDelay, wrappedFn)
- return function() breakFlag = true end
-end
-
return aux_util
diff --git a/files/lua_api/openmw/async.lua b/files/lua_api/openmw/async.lua
index cc3a233f8a..61d5763a0b 100644
--- a/files/lua_api/openmw/async.lua
+++ b/files/lua_api/openmw/async.lua
@@ -15,35 +15,35 @@
-- @return #TimerCallback
-------------------------------------------------------------------------------
--- Calls callback(arg) in `delay` seconds.
+-- Calls callback(arg) in `delay` simulation seconds.
-- Callback must be registered in advance.
--- @function [parent=#async] newTimerInSeconds
+-- @function [parent=#async] newSimulationTimer
-- @param self
-- @param #number delay
-- @param #TimerCallback callback A callback returned by `registerTimerCallback`
-- @param arg An argument for `callback`; can be `nil`.
-------------------------------------------------------------------------------
--- Calls callback(arg) in `delay` game hours.
+-- Calls callback(arg) in `delay` game seconds.
-- Callback must be registered in advance.
--- @function [parent=#async] newTimerInHours
+-- @function [parent=#async] newGameTimer
-- @param self
-- @param #number delay
-- @param #TimerCallback callback A callback returned by `registerTimerCallback`
-- @param arg An argument for `callback`; can be `nil`.
-------------------------------------------------------------------------------
--- Calls `func()` in `delay` seconds.
+-- Calls `func()` in `delay` simulation seconds.
-- The timer will be lost if the game is saved and loaded.
--- @function [parent=#async] newUnsavableTimerInSeconds
+-- @function [parent=#async] newUnsavableSimulationTimer
-- @param self
-- @param #number delay
-- @param #function func
-------------------------------------------------------------------------------
--- Calls `func()` in `delay` game hours.
+-- Calls `func()` in `delay` game seconds.
-- The timer will be lost if the game is saved and loaded.
--- @function [parent=#async] newUnsavableTimerInHours
+-- @function [parent=#async] newUnsavableGameTimer
-- @param self
-- @param #number delay
-- @param #function func
diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua
index 016267d39d..d18be52406 100644
--- a/files/lua_api/openmw/core.lua
+++ b/files/lua_api/openmw/core.lua
@@ -21,15 +21,24 @@
-- @param eventData
-------------------------------------------------------------------------------
+-- Simulation time in seconds.
+-- The number of simulation seconds passed in the game world since starting a new game.
+-- @function [parent=#core] getSimulationTime
+-- @return #number
+
+-------------------------------------------------------------------------------
+-- The scale of simulation time relative to real time.
+-- @function [parent=#core] getSimulationTimeScale
+-- @return #number
+
+-------------------------------------------------------------------------------
-- Game time in seconds.
--- The number of seconds passed in the game world since starting a new game.
--- @function [parent=#core] getGameTimeInSeconds
+-- @function [parent=#core] getGameTime
-- @return #number
-------------------------------------------------------------------------------
--- Current time of the game world in hours.
--- Note that the number of game seconds in a game hour is not guaranteed to be fixed.
--- @function [parent=#core] getGameTimeInHours
+-- The scale of game time relative to simulation time.
+-- @function [parent=#core] getGameTimeScale
-- @return #number
-------------------------------------------------------------------------------
diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua
index 90ea23b4f5..690f0bd566 100644
--- a/files/lua_api/openmw/world.lua
+++ b/files/lua_api/openmw/world.lua
@@ -29,6 +29,36 @@
-- @param #number gridY
-- @return openmw.core#Cell
+-------------------------------------------------------------------------------
+-- Simulation time in seconds.
+-- The number of simulation seconds passed in the game world since starting a new game.
+-- @function [parent=#core] getSimulationTime
+-- @return #number
+
+-------------------------------------------------------------------------------
+-- The scale of simulation time relative to real time.
+-- @function [parent=#core] getSimulationTimeScale
+-- @return #number
+
+-------------------------------------------------------------------------------
+-- Game time in seconds.
+-- @function [parent=#core] getGameTime
+-- @return #number
+
+-------------------------------------------------------------------------------
+-- The scale of game time relative to simulation time.
+-- @function [parent=#core] getGameTimeScale
+-- @return #number
+
+-------------------------------------------------------------------------------
+-- Set the ratio of game time speed to simulation time speed.
+-- @function [parent=#world] setGameTimeScale
+-- @param #number ratio
+
+-------------------------------------------------------------------------------
+-- Whether the world is paused (onUpdate doesn't work when the world is paused).
+-- @function [parent=#world] isWorldPaused
+-- @return #boolean
return nil
diff --git a/files/lua_api/os.doclua b/files/lua_api/os.doclua
new file mode 100644
index 0000000000..127ccd049a
--- /dev/null
+++ b/files/lua_api/os.doclua
@@ -0,0 +1,64 @@
+-------------------------------------------------------------------------------
+-- Operating System Facilities.
+-- This library is implemented through table os.
+-- @module os
+
+
+-------------------------------------------------------------------------------
+-- Returns a string or a table containing date and time, formatted according
+-- to the given string `format`.
+--
+-- If the `time` argument is present, this is the time to be formatted
+-- (see the `os.time` function for a description of this value). Otherwise,
+-- `date` formats the current time.
+--
+-- If `format` starts with '`!`', then the date is formatted in Coordinated
+-- Universal Time. After this optional character, if `format` is the string
+-- "`*t`", then `date` returns a table with the following fields:
+--
+-- * `year` (four digits)
+-- * `month` (1--12)
+-- * `day` (1--31)
+-- * `hour` (0--23)
+-- * `min` (0--59)
+-- * `sec` (0--61)
+-- * `wday` (weekday, Sunday is 1)
+-- * `yday` (day of the year)
+-- * `isdst` (daylight saving flag, a boolean).
+--
+-- If `format` is not "`*t`", then `date` returns the date as a string,
+-- formatted according to the same rules as the C function `strftime`.
+-- When called without arguments, `date` returns a reasonable date and time
+-- representation that depends on the host system and on the current locale
+-- (that is, `os.date()` is equivalent to `os.date("%c")`).
+-- @function [parent=#os] date
+-- @param #string format format of date. (optional)
+-- @param #number time time to format. (default value is current time)
+-- @return #string a formatted string representation of `time`.
+
+-------------------------------------------------------------------------------
+-- Returns the number of seconds from time `t1` to time `t2`. In POSIX,
+-- Windows, and some other systems, this value is exactly `t2`*-*`t1`.
+-- @function [parent=#os] difftime
+-- @param #number t2
+-- @param #number t1
+-- @return #number the number of seconds from time `t1` to time `t2`.
+
+-------------------------------------------------------------------------------
+-- Returns the current time when called without arguments, or a time
+-- representing the date and time specified by the given table. This table
+-- must have fields `year`, `month`, and `day`, and may have fields `hour`,
+-- `min`, `sec`, and `isdst` (for a description of these fields, see the
+-- `os.date` function).
+--
+-- The returned value is a number, whose meaning depends on your system. In
+-- POSIX, Windows, and some other systems, this number counts the number
+-- of seconds since some given start time (the "epoch"). In other systems,
+-- the meaning is not specified, and the number returned by `time` can be
+-- used only as an argument to `date` and `difftime`.
+-- @function [parent=#os] time
+-- @param #table table a table which describes a date.
+-- @return #number a number meaning a date.
+
+return nil
+