mirror of
https://github.com/OpenMW/openmw.git
synced 2025-03-03 07:09:40 +00:00
Change terminology of gameSecond/gameHour to simulationTime/gameTime
This commit is contained in:
parent
8ee8f81619
commit
2d1b100239
20 changed files with 342 additions and 164 deletions
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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.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(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.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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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**
|
||||
|
||||
|
|
5
docs/source/reference/lua-scripting/openmw_aux_time.rst
Normal file
5
docs/source/reference/lua-scripting/openmw_aux_time.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
Package openmw_aux.time
|
||||
=======================
|
||||
|
||||
.. raw:: html
|
||||
:file: generated_html/openmw_aux_time.html
|
|
@ -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:
|
||||
|
||||
|
|
104
files/builtin_scripts/openmw_aux/time.lua
Normal file
104
files/builtin_scripts/openmw_aux/time.lua
Normal file
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -21,15 +21,24 @@
|
|||
-- @param eventData
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Game time in seconds.
|
||||
-- The number of seconds passed in the game world since starting a new game.
|
||||
-- @function [parent=#core] getGameTimeInSeconds
|
||||
-- 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
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- 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 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
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
64
files/lua_api/os.doclua
Normal file
64
files/lua_api/os.doclua
Normal file
|
@ -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
|
||||
|
Loading…
Reference in a new issue