Draft: add new type of Lua scripts - menu scripts

ini_importer_tests
Petr Mikheev 1 year ago
parent d84caff94d
commit 1dd7a15255

@ -61,10 +61,13 @@ add_openmw_dir (mwscript
add_openmw_dir (mwlua add_openmw_dir (mwlua
luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant
context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings
camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings
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 postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings
worker magicbindings factionbindings 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
) )
add_openmw_dir (mwsound add_openmw_dir (mwsound

@ -44,6 +44,9 @@ namespace MWBase
virtual void askLoadRecent() = 0; virtual void askLoadRecent() = 0;
virtual void requestNewGame() = 0;
virtual void requestLoad(const std::filesystem::path& filepath) = 0;
virtual State getState() const = 0; virtual State getState() const = 0;
virtual void newGame(bool bypass = false) = 0; virtual void newGame(bool bypass = false) = 0;

@ -164,7 +164,8 @@ namespace MWBase
virtual void setConsoleSelectedObject(const MWWorld::Ptr& object) = 0; virtual void setConsoleSelectedObject(const MWWorld::Ptr& object) = 0;
virtual MWWorld::Ptr getConsoleSelectedObject() const = 0; virtual MWWorld::Ptr getConsoleSelectedObject() const = 0;
virtual void setConsoleMode(const std::string& mode) = 0; virtual void setConsoleMode(std::string_view mode) = 0;
virtual const std::string& getConsoleMode() = 0;
static constexpr std::string_view sConsoleColor_Default = "#FFFFFF"; static constexpr std::string_view sConsoleColor_Default = "#FFFFFF";
static constexpr std::string_view sConsoleColor_Error = "#FF2222"; static constexpr std::string_view sConsoleColor_Error = "#FF2222";

@ -2173,11 +2173,16 @@ namespace MWGui
mConsole->print(msg, color); mConsole->print(msg, color);
} }
void WindowManager::setConsoleMode(const std::string& mode) void WindowManager::setConsoleMode(std::string_view mode)
{ {
mConsole->setConsoleMode(mode); mConsole->setConsoleMode(mode);
} }
const std::string& WindowManager::getConsoleMode()
{
return mConsole->getConsoleMode();
}
void WindowManager::createCursors() void WindowManager::createCursors()
{ {
MyGUI::ResourceManager::EnumeratorPtr enumerator = MyGUI::ResourceManager::getInstance().getEnumerator(); MyGUI::ResourceManager::EnumeratorPtr enumerator = MyGUI::ResourceManager::getInstance().getEnumerator();

@ -191,7 +191,8 @@ namespace MWGui
void setConsoleSelectedObject(const MWWorld::Ptr& object) override; void setConsoleSelectedObject(const MWWorld::Ptr& object) override;
MWWorld::Ptr getConsoleSelectedObject() const override; MWWorld::Ptr getConsoleSelectedObject() const override;
void printToConsole(const std::string& msg, std::string_view color) override; void printToConsole(const std::string& msg, std::string_view color) override;
void setConsoleMode(const std::string& mode) override; void setConsoleMode(std::string_view mode) override;
const std::string& getConsoleMode() override;
/// Set time left for the player to start drowning (update the drowning bar) /// Set time left for the player to start drowning (update the drowning bar)
/// @param time time left to start drowning /// @param time time left to start drowning

@ -0,0 +1,136 @@
#include "corebindings.hpp"
#include <chrono>
#include <components/debug/debuglog.hpp>
#include <components/esm3/loadfact.hpp>
#include <components/lua/l10n.hpp>
#include <components/lua/luastate.hpp>
#include <components/lua/serialization.hpp>
#include <components/misc/strings/algorithm.hpp>
#include <components/misc/strings/lower.hpp>
#include <components/version/version.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "../mwworld/esmstore.hpp"
#include "factionbindings.hpp"
#include "luaevents.hpp"
#include "magicbindings.hpp"
#include "soundbindings.hpp"
#include "stats.hpp"
namespace MWLua
{
static sol::table initContentFilesBindings(sol::state_view& lua)
{
const std::vector<std::string>& contentList = MWBase::Environment::get().getWorld()->getContentFiles();
sol::table list(lua, sol::create);
for (size_t i = 0; i < contentList.size(); ++i)
list[i + 1] = Misc::StringUtils::lowerCase(contentList[i]);
sol::table res(lua, sol::create);
res["list"] = LuaUtil::makeReadOnly(list);
res["indexOf"] = [&contentList](std::string_view contentFile) -> sol::optional<int> {
for (size_t i = 0; i < contentList.size(); ++i)
if (Misc::StringUtils::ciEqual(contentList[i], contentFile))
return i + 1;
return sol::nullopt;
};
res["has"] = [&contentList](std::string_view contentFile) -> bool {
for (size_t i = 0; i < contentList.size(); ++i)
if (Misc::StringUtils::ciEqual(contentList[i], contentFile))
return true;
return false;
};
return LuaUtil::makeReadOnly(res);
}
void addCoreTimeBindings(sol::table& api, const Context& context)
{
MWWorld::DateTimeManager* timeManager = MWBase::Environment::get().getWorld()->getTimeManager();
api["getSimulationTime"] = [timeManager]() { return timeManager->getSimulationTime(); };
api["getSimulationTimeScale"] = [timeManager]() { return timeManager->getSimulationTimeScale(); };
api["getGameTime"] = [timeManager]() { return timeManager->getGameTime(); };
api["getGameTimeScale"] = [timeManager]() { return timeManager->getGameTimeScale(); };
api["isWorldPaused"] = [timeManager]() { return timeManager->isPaused(); };
api["getRealTime"] = []() {
return std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count();
};
}
sol::table initCorePackage(const Context& context)
{
auto* lua = context.mLua;
if (lua->sol()["openmw_core"] != sol::nil)
return lua->sol()["openmw_core"];
sol::table api(lua->sol(), sol::create);
api["API_REVISION"] = Version::getLuaApiRevision(); // specified in CMakeLists.txt
api["quit"] = [lua]() {
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
MWBase::Environment::get().getStateManager()->requestQuit();
};
api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) {
context.mLuaEvents->addGlobalEvent(
{ std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) });
};
api["contentFiles"] = initContentFilesBindings(lua->sol());
api["sound"] = initCoreSoundBindings(context);
api["getFormId"] = [](std::string_view contentFile, unsigned int index) -> std::string {
const std::vector<std::string>& contentList = MWBase::Environment::get().getWorld()->getContentFiles();
for (size_t i = 0; i < contentList.size(); ++i)
if (Misc::StringUtils::ciEqual(contentList[i], contentFile))
return ESM::RefId(ESM::FormId{ index, int(i) }).serializeText();
throw std::runtime_error("Content file not found: " + std::string(contentFile));
};
addCoreTimeBindings(api, context);
api["magic"] = initCoreMagicBindings(context);
api["stats"] = initCoreStatsBindings(context);
initCoreFactionBindings(context);
api["factions"] = &MWBase::Environment::get().getESMStore()->get<ESM::Faction>();
api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager());
const MWWorld::Store<ESM::GameSetting>* gmstStore
= &MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
api["getGMST"] = [lua = context.mLua, gmstStore](const std::string& setting) -> sol::object {
const ESM::GameSetting* gmst = gmstStore->search(setting);
if (gmst == nullptr)
return sol::nil;
const ESM::Variant& value = gmst->mValue;
switch (value.getType())
{
case ESM::VT_Float:
return sol::make_object<float>(lua->sol(), value.getFloat());
case ESM::VT_Short:
case ESM::VT_Long:
case ESM::VT_Int:
return sol::make_object<int>(lua->sol(), value.getInteger());
case ESM::VT_String:
return sol::make_object<std::string>(lua->sol(), value.getString());
case ESM::VT_Unknown:
case ESM::VT_None:
break;
}
return sol::nil;
};
lua->sol()["openmw_core"] = LuaUtil::makeReadOnly(api);
return lua->sol()["openmw_core"];
}
sol::table initCorePackageForMenuScripts(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
for (auto& [k, v] : LuaUtil::getMutableFromReadOnly(initCorePackage(context)))
api[k] = v;
api["sendGlobalEvent"] = sol::nil;
api["sound"] = sol::nil;
return LuaUtil::makeReadOnly(api);
}
}

@ -0,0 +1,19 @@
#ifndef MWLUA_COREBINDINGS_H
#define MWLUA_COREBINDINGS_H
#include <sol/forward.hpp>
#include "context.hpp"
namespace MWLua
{
void addCoreTimeBindings(sol::table& api, const Context& context);
sol::table initCorePackage(const Context&);
// Returns `openmw.core`, but disables the functionality that shouldn't
// be availabe in menu scripts (to prevent cheating in mutiplayer via menu console).
sol::table initCorePackageForMenuScripts(const Context&);
}
#endif // MWLUA_COREBINDINGS_H

@ -1,10 +1,6 @@
#ifndef MWLUA_GLOBALSCRIPTS_H #ifndef MWLUA_GLOBALSCRIPTS_H
#define MWLUA_GLOBALSCRIPTS_H #define MWLUA_GLOBALSCRIPTS_H
#include <memory>
#include <set>
#include <string>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/scriptscontainer.hpp> #include <components/lua/scriptscontainer.hpp>

@ -1,328 +1,30 @@
#include "luabindings.hpp" #include "luabindings.hpp"
#include <chrono> #include <components/lua/asyncpackage.hpp>
#include <components/esm/attr.hpp>
#include <components/esm3/loadacti.hpp>
#include <components/esm3/loadalch.hpp>
#include <components/esm3/loadarmo.hpp>
#include <components/esm3/loadbook.hpp>
#include <components/esm3/loadclot.hpp>
#include <components/esm3/loadfact.hpp>
#include <components/esm3/loadmisc.hpp>
#include <components/esm3/loadskil.hpp>
#include <components/esm3/loadweap.hpp>
#include <components/lua/l10n.hpp>
#include <components/lua/luastate.hpp>
#include <components/lua/utilpackage.hpp> #include <components/lua/utilpackage.hpp>
#include <components/misc/strings/lower.hpp>
#include <components/version/version.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/statemanager.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwworld/action.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/datetimemanager.hpp" #include "../mwworld/datetimemanager.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/manualref.hpp"
#include "../mwworld/store.hpp"
#include "../mwworld/worldmodel.hpp"
#include "luaevents.hpp"
#include "luamanagerimp.hpp"
#include "mwscriptbindings.hpp"
#include "objectlists.hpp"
#include "camerabindings.hpp" #include "camerabindings.hpp"
#include "cellbindings.hpp" #include "cellbindings.hpp"
#include "corebindings.hpp"
#include "debugbindings.hpp" #include "debugbindings.hpp"
#include "factionbindings.hpp"
#include "inputbindings.hpp" #include "inputbindings.hpp"
#include "magicbindings.hpp" #include "localscripts.hpp"
#include "menuscripts.hpp"
#include "nearbybindings.hpp" #include "nearbybindings.hpp"
#include "objectbindings.hpp" #include "objectbindings.hpp"
#include "postprocessingbindings.hpp" #include "postprocessingbindings.hpp"
#include "soundbindings.hpp" #include "soundbindings.hpp"
#include "stats.hpp"
#include "types/types.hpp" #include "types/types.hpp"
#include "uibindings.hpp" #include "uibindings.hpp"
#include "vfsbindings.hpp" #include "vfsbindings.hpp"
#include "worldbindings.hpp"
namespace MWLua namespace MWLua
{ {
struct CellsStore
{
};
}
namespace sol
{
template <>
struct is_automagical<MWLua::CellsStore> : std::false_type
{
};
}
namespace MWLua
{
static void checkGameInitialized(LuaUtil::LuaState* lua)
{
if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame)
throw std::runtime_error(
"This function cannot be used until the game is fully initialized.\n" + lua->debugTraceback());
}
static void addTimeBindings(sol::table& api, const Context& context, bool global)
{
MWWorld::DateTimeManager* timeManager = MWBase::Environment::get().getWorld()->getTimeManager();
api["getSimulationTime"] = [timeManager]() { return timeManager->getSimulationTime(); };
api["getSimulationTimeScale"] = [timeManager]() { return timeManager->getSimulationTimeScale(); };
api["getGameTime"] = [timeManager]() { return timeManager->getGameTime(); };
api["getGameTimeScale"] = [timeManager]() { return timeManager->getGameTimeScale(); };
api["isWorldPaused"] = [timeManager]() { return timeManager->isPaused(); };
api["getRealTime"] = []() {
return std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count();
};
if (!global)
return;
api["setGameTimeScale"] = [timeManager](double scale) { timeManager->setGameTimeScale(scale); };
api["setSimulationTimeScale"] = [context, timeManager](float scale) {
context.mLuaManager->addAction([scale, timeManager] { timeManager->setSimulationTimeScale(scale); });
};
api["pause"]
= [timeManager](sol::optional<std::string_view> tag) { timeManager->pause(tag.value_or("paused")); };
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)
{
const std::vector<std::string>& contentList = MWBase::Environment::get().getWorld()->getContentFiles();
sol::table list(lua, sol::create);
for (size_t i = 0; i < contentList.size(); ++i)
list[i + 1] = Misc::StringUtils::lowerCase(contentList[i]);
sol::table res(lua, sol::create);
res["list"] = LuaUtil::makeReadOnly(list);
res["indexOf"] = [&contentList](std::string_view contentFile) -> sol::optional<int> {
for (size_t i = 0; i < contentList.size(); ++i)
if (Misc::StringUtils::ciEqual(contentList[i], contentFile))
return i + 1;
return sol::nullopt;
};
res["has"] = [&contentList](std::string_view contentFile) -> bool {
for (size_t i = 0; i < contentList.size(); ++i)
if (Misc::StringUtils::ciEqual(contentList[i], contentFile))
return true;
return false;
};
return LuaUtil::makeReadOnly(res);
}
static sol::table initCorePackage(const Context& context)
{
auto* lua = context.mLua;
sol::table api(lua->sol(), sol::create);
api["API_REVISION"] = Version::getLuaApiRevision(); // specified in CMakeLists.txt
api["quit"] = [lua]() {
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
MWBase::Environment::get().getStateManager()->requestQuit();
};
api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) {
context.mLuaEvents->addGlobalEvent(
{ std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) });
};
api["contentFiles"] = initContentFilesBindings(lua->sol());
api["sound"] = initCoreSoundBindings(context);
api["getFormId"] = [](std::string_view contentFile, unsigned int index) -> std::string {
const std::vector<std::string>& contentList = MWBase::Environment::get().getWorld()->getContentFiles();
for (size_t i = 0; i < contentList.size(); ++i)
if (Misc::StringUtils::ciEqual(contentList[i], contentFile))
return ESM::RefId(ESM::FormId{ index, int(i) }).serializeText();
throw std::runtime_error("Content file not found: " + std::string(contentFile));
};
addTimeBindings(api, context, false);
api["magic"] = initCoreMagicBindings(context);
api["stats"] = initCoreStatsBindings(context);
initCoreFactionBindings(context);
api["factions"] = &MWBase::Environment::get().getWorld()->getStore().get<ESM::Faction>();
api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager());
const MWWorld::Store<ESM::GameSetting>* gmstStore
= &MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
api["getGMST"] = [lua = context.mLua, gmstStore](const std::string& setting) -> sol::object {
const ESM::GameSetting* gmst = gmstStore->search(setting);
if (gmst == nullptr)
return sol::nil;
const ESM::Variant& value = gmst->mValue;
switch (value.getType())
{
case ESM::VT_Float:
return sol::make_object<float>(lua->sol(), value.getFloat());
case ESM::VT_Short:
case ESM::VT_Long:
case ESM::VT_Int:
return sol::make_object<int>(lua->sol(), value.getInteger());
case ESM::VT_String:
return sol::make_object<std::string>(lua->sol(), value.getString());
case ESM::VT_Unknown:
case ESM::VT_None:
break;
}
return sol::nil;
};
return LuaUtil::makeReadOnly(api);
}
static void addCellGetters(sol::table& api, const Context& context)
{
api["getCellByName"] = [](std::string_view name) {
return GCell{ &MWBase::Environment::get().getWorldModel()->getCell(name, /*forceLoad=*/false) };
};
api["getExteriorCell"] = [](int x, int y, sol::object cellOrName) {
ESM::RefId worldspace;
if (cellOrName.is<GCell>())
worldspace = cellOrName.as<GCell>().mStore->getCell()->getWorldSpace();
else if (cellOrName.is<std::string_view>() && !cellOrName.as<std::string_view>().empty())
worldspace = MWBase::Environment::get()
.getWorldModel()
->getCell(cellOrName.as<std::string_view>())
.getCell()
->getWorldSpace();
else
worldspace = ESM::Cell::sDefaultWorldspaceId;
return GCell{ &MWBase::Environment::get().getWorldModel()->getExterior(
ESM::ExteriorCellLocation(x, y, worldspace), /*forceLoad=*/false) };
};
const MWWorld::Store<ESM::Cell>* cells3Store = &MWBase::Environment::get().getESMStore()->get<ESM::Cell>();
const MWWorld::Store<ESM4::Cell>* cells4Store = &MWBase::Environment::get().getESMStore()->get<ESM4::Cell>();
sol::usertype<CellsStore> cells = context.mLua->sol().new_usertype<CellsStore>("Cells");
cells[sol::meta_function::length]
= [cells3Store, cells4Store](const CellsStore&) { return cells3Store->getSize() + cells4Store->getSize(); };
cells[sol::meta_function::index]
= [cells3Store, cells4Store](const CellsStore&, size_t index) -> sol::optional<GCell> {
if (index > cells3Store->getSize() + cells3Store->getSize() || index == 0)
return sol::nullopt;
index--; // Translate from Lua's 1-based indexing.
if (index < cells3Store->getSize())
{
const ESM::Cell* cellRecord = cells3Store->at(index);
return GCell{ &MWBase::Environment::get().getWorldModel()->getCell(
cellRecord->mId, /*forceLoad=*/false) };
}
else
{
const ESM4::Cell* cellRecord = cells4Store->at(index - cells3Store->getSize());
return GCell{ &MWBase::Environment::get().getWorldModel()->getCell(
cellRecord->mId, /*forceLoad=*/false) };
}
};
cells[sol::meta_function::pairs] = context.mLua->sol()["ipairsForArray"].template get<sol::function>();
cells[sol::meta_function::ipairs] = context.mLua->sol()["ipairsForArray"].template get<sol::function>();
api["cells"] = CellsStore{};
}
static sol::table initWorldPackage(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
ObjectLists* objectLists = context.mObjectLists;
addTimeBindings(api, context, true);
addCellGetters(api, context);
api["mwscript"] = initMWScriptBindings(context);
api["activeActors"] = GObjectList{ objectLists->getActorsInScene() };
api["players"] = GObjectList{ objectLists->getPlayers() };
api["createObject"] = [lua = context.mLua](std::string_view recordId, sol::optional<int> count) -> GObject {
checkGameInitialized(lua);
MWWorld::ManualRef mref(*MWBase::Environment::get().getESMStore(), ESM::RefId::deserializeText(recordId));
const MWWorld::Ptr& ptr = mref.getPtr();
ptr.getRefData().disable();
MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getDraftCell();
MWWorld::Ptr newPtr = ptr.getClass().copyToCell(ptr, cell, count.value_or(1));
return GObject(newPtr);
};
api["getObjectByFormId"] = [](std::string_view formIdStr) -> GObject {
ESM::RefId refId = ESM::RefId::deserializeText(formIdStr);
if (!refId.is<ESM::FormId>())
throw std::runtime_error("FormId expected, got " + std::string(formIdStr) + "; use core.getFormId");
return GObject(*refId.getIf<ESM::FormId>());
};
// Creates a new record in the world database.
api["createRecord"] = sol::overload(
[lua = context.mLua](const ESM::Activator& activator) -> const ESM::Activator* {
checkGameInitialized(lua);
return MWBase::Environment::get().getESMStore()->insert(activator);
},
[lua = context.mLua](const ESM::Armor& armor) -> const ESM::Armor* {
checkGameInitialized(lua);
return MWBase::Environment::get().getESMStore()->insert(armor);
},
[lua = context.mLua](const ESM::Clothing& clothing) -> const ESM::Clothing* {
checkGameInitialized(lua);
return MWBase::Environment::get().getESMStore()->insert(clothing);
},
[lua = context.mLua](const ESM::Book& book) -> const ESM::Book* {
checkGameInitialized(lua);
return MWBase::Environment::get().getESMStore()->insert(book);
},
[lua = context.mLua](const ESM::Miscellaneous& misc) -> const ESM::Miscellaneous* {
checkGameInitialized(lua);
return MWBase::Environment::get().getESMStore()->insert(misc);
},
[lua = context.mLua](const ESM::Potion& potion) -> const ESM::Potion* {
checkGameInitialized(lua);
return MWBase::Environment::get().getESMStore()->insert(potion);
},
[lua = context.mLua](const ESM::Weapon& weapon) -> const ESM::Weapon* {
checkGameInitialized(lua);
return MWBase::Environment::get().getESMStore()->insert(weapon);
});
api["_runStandardActivationAction"] = [context](const GObject& object, const GObject& actor) {
if (!object.ptr().getRefData().activate())
return;
context.mLuaManager->addAction(
[object, actor] {
const MWWorld::Ptr& objPtr = object.ptr();
const MWWorld::Ptr& actorPtr = actor.ptr();
objPtr.getClass().activate(objPtr, actorPtr)->execute(actorPtr);
},
"_runStandardActivationAction");
};
api["_runStandardUseAction"] = [context](const GObject& object, const GObject& actor, bool force) {
context.mLuaManager->addAction(
[object, actor, force] {
const MWWorld::Ptr& actorPtr = actor.ptr();
const MWWorld::Ptr& objectPtr = object.ptr();
if (actorPtr == MWBase::Environment::get().getWorld()->getPlayerPtr())
MWBase::Environment::get().getWindowManager()->useItem(objectPtr, force);
else
{
std::unique_ptr<MWWorld::Action> action = objectPtr.getClass().use(objectPtr, force);
action->execute(actorPtr, true);
}
},
"_runStandardUseAction");
};
return LuaUtil::makeReadOnly(api);
}
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();
@ -331,8 +33,6 @@ namespace MWLua
{ "openmw.async", { "openmw.async",
LuaUtil::getAsyncPackageInitializer( LuaUtil::getAsyncPackageInitializer(
lua, [tm] { return tm->getSimulationTime(); }, [tm] { return tm->getGameTime(); }) }, lua, [tm] { return tm->getSimulationTime(); }, [tm] { return tm->getGameTime(); }) },
{ "openmw.core", initCorePackage(context) },
{ "openmw.types", initTypesPackage(context) },
{ "openmw.util", LuaUtil::initUtilPackage(lua) }, { "openmw.util", LuaUtil::initUtilPackage(lua) },
{ "openmw.vfs", initVFSPackage(context) }, { "openmw.vfs", initVFSPackage(context) },
}; };
@ -343,6 +43,8 @@ namespace MWLua
initObjectBindingsForGlobalScripts(context); initObjectBindingsForGlobalScripts(context);
initCellBindingsForGlobalScripts(context); initCellBindingsForGlobalScripts(context);
return { return {
{ "openmw.core", initCorePackage(context) },
{ "openmw.types", initTypesPackage(context) },
{ "openmw.world", initWorldPackage(context) }, { "openmw.world", initWorldPackage(context) },
}; };
} }
@ -353,6 +55,8 @@ namespace MWLua
initCellBindingsForLocalScripts(context); initCellBindingsForLocalScripts(context);
LocalScripts::initializeSelfPackage(context); LocalScripts::initializeSelfPackage(context);
return { return {
{ "openmw.core", initCorePackage(context) },
{ "openmw.types", initTypesPackage(context) },
{ "openmw.nearby", initNearbyPackage(context) }, { "openmw.nearby", initNearbyPackage(context) },
}; };
} }
@ -369,4 +73,16 @@ namespace MWLua
}; };
} }
std::map<std::string, sol::object> initMenuPackages(const Context& context)
{
return {
{ "openmw.core", initCorePackageForMenuScripts(context) }, //
{ "openmw.ambient", initAmbientPackage(context) }, //
{ "openmw.ui", initUserInterfacePackage(context) }, //
{ "openmw.menu", initMenuPackage(context) },
// TODO: Maybe add:
// { "openmw.input", initInputPackage(context) },
// { "openmw.postprocessing", initPostprocessingPackage(context) },
};
}
} }

@ -12,14 +12,18 @@ namespace MWLua
// Initialize Lua packages that are available for all scripts. // Initialize Lua packages that are available for all scripts.
std::map<std::string, sol::object> initCommonPackages(const Context&); std::map<std::string, sol::object> initCommonPackages(const Context&);
// Initialize Lua packages that are available only for global scripts. // Initialize Lua packages that are available for global scripts (additionally to common packages).
std::map<std::string, sol::object> initGlobalPackages(const Context&); std::map<std::string, sol::object> initGlobalPackages(const Context&);
// Initialize Lua packages that are available only for local scripts (including player scripts). // Initialize Lua packages that are available for local scripts (additionally to common packages).
std::map<std::string, sol::object> initLocalPackages(const Context&); std::map<std::string, sol::object> initLocalPackages(const Context&);
// Initialize Lua packages that are available only for local scripts on the player. // Initialize Lua packages that are available only for local scripts on the player (additionally to common and local
// packages).
std::map<std::string, sol::object> initPlayerPackages(const Context&); std::map<std::string, sol::object> initPlayerPackages(const Context&);
// Initialize Lua packages that are available only for menu scripts (additionally to common packages).
std::map<std::string, sol::object> initMenuPackages(const Context&);
} }
#endif // MWLUA_LUABINDINGS_H #endif // MWLUA_LUABINDINGS_H

@ -68,6 +68,7 @@ namespace MWLua
Log(Debug::Verbose) << "Lua scripts configuration (" << mConfiguration.size() << " scripts):"; Log(Debug::Verbose) << "Lua scripts configuration (" << mConfiguration.size() << " scripts):";
for (size_t i = 0; i < mConfiguration.size(); ++i) for (size_t i = 0; i < mConfiguration.size(); ++i)
Log(Debug::Verbose) << "#" << i << " " << LuaUtil::scriptCfgToString(mConfiguration[i]); Log(Debug::Verbose) << "#" << i << " " << LuaUtil::scriptCfgToString(mConfiguration[i]);
mMenuScripts.setAutoStartConf(mConfiguration.getMenuConf());
mGlobalScripts.setAutoStartConf(mConfiguration.getGlobalConf()); mGlobalScripts.setAutoStartConf(mConfiguration.getGlobalConf());
} }
@ -89,20 +90,25 @@ namespace MWLua
mLua.addCommonPackage(name, package); mLua.addCommonPackage(name, package);
for (const auto& [name, package] : initGlobalPackages(context)) for (const auto& [name, package] : initGlobalPackages(context))
mGlobalScripts.addPackage(name, package); mGlobalScripts.addPackage(name, package);
for (const auto& [name, package] : initMenuPackages(context))
mMenuScripts.addPackage(name, package);
mLocalPackages = initLocalPackages(localContext); mLocalPackages = initLocalPackages(localContext);
mPlayerPackages = initPlayerPackages(localContext); mPlayerPackages = initPlayerPackages(localContext);
mPlayerPackages.insert(mLocalPackages.begin(), mLocalPackages.end()); mPlayerPackages.insert(mLocalPackages.begin(), mLocalPackages.end());
LuaUtil::LuaStorage::initLuaBindings(mLua.sol()); LuaUtil::LuaStorage::initLuaBindings(mLua.sol());
mGlobalScripts.addPackage( mGlobalScripts.addPackage(
"openmw.storage", LuaUtil::LuaStorage::initGlobalPackage(mLua.sol(), &mGlobalStorage)); "openmw.storage", LuaUtil::LuaStorage::initGlobalPackage(mLua.sol(), &mGlobalStorage));
mMenuScripts.addPackage("openmw.storage", LuaUtil::LuaStorage::initMenuPackage(mLua.sol(), &mPlayerStorage));
mLocalPackages["openmw.storage"] = LuaUtil::LuaStorage::initLocalPackage(mLua.sol(), &mGlobalStorage); mLocalPackages["openmw.storage"] = LuaUtil::LuaStorage::initLocalPackage(mLua.sol(), &mGlobalStorage);
mPlayerPackages["openmw.storage"] mPlayerPackages["openmw.storage"]
= LuaUtil::LuaStorage::initPlayerPackage(mLua.sol(), &mGlobalStorage, &mPlayerStorage); = LuaUtil::LuaStorage::initPlayerPackage(mLua.sol(), &mGlobalStorage, &mPlayerStorage);
initConfiguration(); initConfiguration();
mInitialized = true; mInitialized = true;
mMenuScripts.addAutoStartedScripts();
} }
void LuaManager::loadPermanentStorage(const std::filesystem::path& userConfigPath) void LuaManager::loadPermanentStorage(const std::filesystem::path& userConfigPath)
@ -204,9 +210,6 @@ namespace MWLua
void LuaManager::synchronizedUpdate() void LuaManager::synchronizedUpdate()
{ {
if (mPlayer.isEmpty())
return; // The game is not started yet.
if (mNewGameStarted) if (mNewGameStarted)
{ {
mNewGameStarted = false; mNewGameStarted = false;
@ -217,7 +220,8 @@ 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
= mPlayer.isEmpty() ? nullptr : dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts());
MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager();
if (playerScripts && !windowManager->containsMode(MWGui::GM_MainMenu)) if (playerScripts && !windowManager->containsMode(MWGui::GM_MainMenu))
{ {
@ -225,6 +229,7 @@ namespace MWLua
playerScripts->processInputEvent(event); playerScripts->processInputEvent(event);
} }
mInputEvents.clear(); mInputEvents.clear();
mMenuScripts.update(0);
if (playerScripts) if (playerScripts)
playerScripts->onFrame(MWBase::Environment::get().getWorld()->getTimeManager()->isPaused() playerScripts->onFrame(MWBase::Environment::get().getWorld()->getTimeManager()->isPaused()
? 0.0 ? 0.0
@ -272,7 +277,6 @@ namespace MWLua
{ {
LuaUi::clearUserInterface(); LuaUi::clearUserInterface();
mUiResourceManager.clear(); mUiResourceManager.clear();
MWBase::Environment::get().getWindowManager()->setConsoleMode("");
MWBase::Environment::get().getWorld()->getPostProcessor()->disableDynamicShaders(); MWBase::Environment::get().getWorld()->getPostProcessor()->disableDynamicShaders();
mActiveLocalScripts.clear(); mActiveLocalScripts.clear();
mLuaEvents.clear(); mLuaEvents.clear();
@ -320,6 +324,7 @@ namespace MWLua
mGlobalScripts.addAutoStartedScripts(); mGlobalScripts.addAutoStartedScripts();
mGlobalScriptsStarted = true; mGlobalScriptsStarted = true;
mNewGameStarted = true; mNewGameStarted = true;
mMenuScripts.stateChanged();
} }
void LuaManager::gameLoaded() void LuaManager::gameLoaded()
@ -327,6 +332,7 @@ namespace MWLua
if (!mGlobalScriptsStarted) if (!mGlobalScriptsStarted)
mGlobalScripts.addAutoStartedScripts(); mGlobalScripts.addAutoStartedScripts();
mGlobalScriptsStarted = true; mGlobalScriptsStarted = true;
mMenuScripts.stateChanged();
} }
void LuaManager::uiModeChanged(const MWWorld::Ptr& arg) void LuaManager::uiModeChanged(const MWWorld::Ptr& arg)
@ -529,6 +535,9 @@ namespace MWLua
} }
for (LocalScripts* scripts : mActiveLocalScripts) for (LocalScripts* scripts : mActiveLocalScripts)
scripts->setActive(true); scripts->setActive(true);
mMenuScripts.removeAllScripts();
mMenuScripts.addAutoStartedScripts();
} }
void LuaManager::handleConsoleCommand( void LuaManager::handleConsoleCommand(
@ -537,16 +546,16 @@ namespace MWLua
PlayerScripts* playerScripts = nullptr; PlayerScripts* playerScripts = nullptr;
if (!mPlayer.isEmpty()) if (!mPlayer.isEmpty())
playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts()); playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts());
if (!playerScripts) bool processed = mMenuScripts.consoleCommand(consoleMode, command);
if (playerScripts)
{ {
MWBase::Environment::get().getWindowManager()->printToConsole( sol::object selected = sol::nil;
"You must enter a game session to run Lua commands\n", MWBase::WindowManager::sConsoleColor_Error); if (!selectedPtr.isEmpty())
return; selected = sol::make_object(mLua.sol(), LObject(getId(selectedPtr)));
if (playerScripts->consoleCommand(consoleMode, command, selected))
processed = true;
} }
sol::object selected = sol::nil; if (!processed)
if (!selectedPtr.isEmpty())
selected = sol::make_object(mLua.sol(), LObject(getId(selectedPtr)));
if (!playerScripts->consoleCommand(consoleMode, command, selected))
MWBase::Environment::get().getWindowManager()->printToConsole( MWBase::Environment::get().getWindowManager()->printToConsole(
"No Lua handlers for console\n", MWBase::WindowManager::sConsoleColor_Error); "No Lua handlers for console\n", MWBase::WindowManager::sConsoleColor_Error);
} }
@ -680,6 +689,7 @@ namespace MWLua
for (size_t i = 0; i < mConfiguration.size(); ++i) for (size_t i = 0; i < mConfiguration.size(); ++i)
{ {
bool isGlobal = mConfiguration[i].mFlags & ESM::LuaScriptCfg::sGlobal; bool isGlobal = mConfiguration[i].mFlags & ESM::LuaScriptCfg::sGlobal;
bool isMenu = mConfiguration[i].mFlags & ESM::LuaScriptCfg::sMenu;
out << std::left; out << std::left;
out << " " << std::setw(nameW) << mConfiguration[i].mScriptPath; out << " " << std::setw(nameW) << mConfiguration[i].mScriptPath;
@ -692,6 +702,8 @@ namespace MWLua
if (isGlobal) if (isGlobal)
out << std::setw(valueW * 2) << "NA (global script)"; out << std::setw(valueW * 2) << "NA (global script)";
else if (isMenu && (!selectedScripts || !selectedScripts->hasScript(i)))
out << std::setw(valueW * 2) << "NA (menu script)";
else if (selectedPtr.isEmpty()) else if (selectedPtr.isEmpty())
out << std::setw(valueW * 2) << "NA (not selected) "; out << std::setw(valueW * 2) << "NA (not selected) ";
else if (!selectedScripts || !selectedScripts->hasScript(i)) else if (!selectedScripts || !selectedScripts->hasScript(i))

@ -17,6 +17,7 @@
#include "globalscripts.hpp" #include "globalscripts.hpp"
#include "localscripts.hpp" #include "localscripts.hpp"
#include "luaevents.hpp" #include "luaevents.hpp"
#include "menuscripts.hpp"
#include "object.hpp" #include "object.hpp"
#include "objectlists.hpp" #include "objectlists.hpp"
@ -164,6 +165,7 @@ namespace MWLua
std::map<std::string, sol::object> mLocalPackages; std::map<std::string, sol::object> mLocalPackages;
std::map<std::string, sol::object> mPlayerPackages; std::map<std::string, sol::object> mPlayerPackages;
MenuScripts mMenuScripts{ &mLua };
GlobalScripts mGlobalScripts{ &mLua }; GlobalScripts mGlobalScripts{ &mLua };
std::set<LocalScripts*> mActiveLocalScripts; std::set<LocalScripts*> mActiveLocalScripts;
ObjectLists mObjectLists; ObjectLists mObjectLists;

@ -0,0 +1,114 @@
#include "menuscripts.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwstate/character.hpp"
namespace MWLua
{
static const MWState::Character* findCharacter(std::string_view characterDir)
{
MWBase::StateManager* manager = MWBase::Environment::get().getStateManager();
for (auto it = manager->characterBegin(); it != manager->characterEnd(); ++it)
if (it->getPath().filename() == characterDir)
return &*it;
return nullptr;
}
static const MWState::Slot* findSlot(const MWState::Character* character, std::string_view slotName)
{
if (!character)
return nullptr;
for (const MWState::Slot& slot : *character)
if (slot.mPath.filename() == slotName)
return &slot;
return nullptr;
}
sol::table initMenuPackage(const Context& context)
{
sol::state_view lua = context.mLua->sol();
sol::table api(lua, sol::create);
api["STATE"]
= LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs<std::string_view, MWBase::StateManager::State>({
{ "NoGame", MWBase::StateManager::State_NoGame },
{ "Running", MWBase::StateManager::State_Running },
{ "Ended", MWBase::StateManager::State_Ended },
}));
api["getState"] = []() -> int { return MWBase::Environment::get().getStateManager()->getState(); };
api["newGame"] = []() { MWBase::Environment::get().getStateManager()->requestNewGame(); };
api["loadGame"] = [](std::string_view dir, std::string_view slotName) {
const MWState::Character* character = findCharacter(dir);
const MWState::Slot* slot = findSlot(character, slotName);
if (!slot)
throw std::runtime_error("Save game slot not found: " + std::string(dir) + "/" + std::string(slotName));
MWBase::Environment::get().getStateManager()->requestLoad(slot->mPath);
};
api["deleteGame"] = [](std::string_view dir, std::string_view slotName) {
const MWState::Character* character = findCharacter(dir);
const MWState::Slot* slot = findSlot(character, slotName);
if (!slot)
throw std::runtime_error("Save game slot not found: " + std::string(dir) + "/" + std::string(slotName));
MWBase::Environment::get().getStateManager()->deleteGame(character, slot);
};
api["getCurrentSaveDir"] = []() -> sol::optional<std::string> {
MWBase::StateManager* manager = MWBase::Environment::get().getStateManager();
const MWState::Character* character = manager->getCurrentCharacter();
if (character)
return character->getPath().filename().string();
else
return sol::nullopt;
};
api["saveGame"] = [](std::string_view description, sol::optional<std::string_view> slotName) {
MWBase::StateManager* manager = MWBase::Environment::get().getStateManager();
const MWState::Character* character = manager->getCurrentCharacter();
const MWState::Slot* slot = nullptr;
if (slotName)
slot = findSlot(character, *slotName);
manager->saveGame(description, slot);
};
auto getSaves = [](sol::state_view lua, const MWState::Character& character) {
sol::table saves(lua, sol::create);
for (const MWState::Slot& slot : character)
{
sol::table slotInfo(lua, sol::create);
slotInfo["description"] = slot.mProfile.mDescription;
slotInfo["playerName"] = slot.mProfile.mPlayerName;
slotInfo["playerLevel"] = slot.mProfile.mPlayerLevel;
sol::table contentFiles(lua, sol::create);
for (size_t i = 0; i < slot.mProfile.mContentFiles.size(); ++i)
contentFiles[i + 1] = slot.mProfile.mContentFiles[i];
slotInfo["contentFiles"] = contentFiles;
saves[slot.mPath.filename().string()] = slotInfo;
}
return saves;
};
api["getSaves"] = [getSaves](sol::this_state lua, std::string_view dir) -> sol::table {
const MWState::Character* character = findCharacter(dir);
if (!character)
throw std::runtime_error("Saves not found: " + std::string(dir));
return getSaves(lua, *character);
};
api["getAllSaves"] = [getSaves](sol::this_state lua) -> sol::table {
sol::table saves(lua, sol::create);
MWBase::StateManager* manager = MWBase::Environment::get().getStateManager();
for (auto it = manager->characterBegin(); it != manager->characterEnd(); ++it)
saves[it->getPath().filename().string()] = getSaves(lua, *it);
return saves;
};
api["quit"] = []() { MWBase::Environment::get().getStateManager()->requestQuit(); };
return LuaUtil::makeReadOnly(api);
}
}

@ -0,0 +1,46 @@
#ifndef MWLUA_MENUSCRIPTS_H
#define MWLUA_MENUSCRIPTS_H
#include <SDL_events.h>
#include <components/lua/luastate.hpp>
#include <components/lua/scriptscontainer.hpp>
#include <components/sdlutil/events.hpp>
#include "../mwbase/luamanager.hpp"
#include "context.hpp"
namespace MWLua
{
sol::table initMenuPackage(const Context& context);
class MenuScripts : public LuaUtil::ScriptsContainer
{
public:
MenuScripts(LuaUtil::LuaState* lua)
: LuaUtil::ScriptsContainer(lua, "Menu")
{
registerEngineHandlers({ &mStateChanged, &mConsoleCommandHandlers, &mUiModeChanged });
}
void stateChanged() { callEngineHandlers(mStateChanged); }
bool consoleCommand(const std::string& consoleMode, const std::string& command)
{
callEngineHandlers(mConsoleCommandHandlers, consoleMode, command);
return !mConsoleCommandHandlers.mList.empty();
}
void uiModeChanged() { callEngineHandlers(mUiModeChanged); }
private:
EngineHandlerList mStateChanged{ "onStateChanged" };
EngineHandlerList mConsoleCommandHandlers{ "onConsoleCommand" };
EngineHandlerList mUiModeChanged{ "_onUiModeChanged" };
};
}
#endif // MWLUA_GLOBALSCRIPTS_H

@ -61,7 +61,11 @@ namespace MWLua
{ {
sol::table initAmbientPackage(const Context& context) sol::table initAmbientPackage(const Context& context)
{ {
sol::table api(context.mLua->sol(), sol::create); sol::state_view& lua = context.mLua->sol();
if (lua["openmw_ambient"] != sol::nil)
return lua["openmw_ambient"];
sol::table api(lua, sol::create);
api["playSound"] = [](std::string_view soundId, const sol::optional<sol::table>& options) { api["playSound"] = [](std::string_view soundId, const sol::optional<sol::table>& options) {
auto args = getPlaySoundArgs(options); auto args = getPlaySoundArgs(options);
@ -104,7 +108,8 @@ namespace MWLua
api["stopMusic"] = []() { MWBase::Environment::get().getSoundManager()->stopMusic(); }; api["stopMusic"] = []() { MWBase::Environment::get().getSoundManager()->stopMusic(); };
return LuaUtil::makeReadOnly(api); lua["openmw_ambient"] = LuaUtil::makeReadOnly(api);
return lua["openmw_ambient"];
} }
sol::table initCoreSoundBindings(const Context& context) sol::table initCoreSoundBindings(const Context& context)

@ -162,6 +162,10 @@ namespace MWLua
sol::table initTypesPackage(const Context& context) sol::table initTypesPackage(const Context& context)
{ {
auto* lua = context.mLua; auto* lua = context.mLua;
if (lua->sol()["openmw_types"] != sol::nil)
return lua->sol()["openmw_types"];
sol::table types(lua->sol(), sol::create); sol::table types(lua->sol(), sol::create);
auto addType = [&](std::string_view name, std::vector<ESM::RecNameInts> recTypes, auto addType = [&](std::string_view name, std::vector<ESM::RecNameInts> recTypes,
std::optional<std::string_view> base = std::nullopt) -> sol::table { std::optional<std::string_view> base = std::nullopt) -> sol::table {
@ -250,6 +254,7 @@ namespace MWLua
packageToType[t] = type; packageToType[t] = type;
} }
return LuaUtil::makeReadOnly(types); lua->sol()["openmw_types"] = LuaUtil::makeReadOnly(types);
return lua->sol()["openmw_types"];
} }
} }

@ -92,6 +92,12 @@ namespace MWLua
sol::table initUserInterfacePackage(const Context& context) sol::table initUserInterfacePackage(const Context& context)
{ {
{
sol::state_view& lua = context.mLua->sol();
if (lua["openmw_ui"] != sol::nil)
return lua["openmw_ui"];
}
MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager();
auto element = context.mLua->sol().new_usertype<LuaUi::Element>("Element"); auto element = context.mLua->sol().new_usertype<LuaUi::Element>("Element");
@ -130,6 +136,7 @@ namespace MWLua
api["setConsoleMode"] = [luaManager = context.mLuaManager, windowManager](std::string_view mode) { api["setConsoleMode"] = [luaManager = context.mLuaManager, windowManager](std::string_view mode) {
luaManager->addAction([mode = std::string(mode), windowManager] { windowManager->setConsoleMode(mode); }); luaManager->addAction([mode = std::string(mode), windowManager] { windowManager->setConsoleMode(mode); });
}; };
api["getConsoleMode"] = [windowManager]() -> std::string_view { return windowManager->getConsoleMode(); };
api["setConsoleSelectedObject"] = [luaManager = context.mLuaManager, windowManager](const sol::object& obj) { api["setConsoleSelectedObject"] = [luaManager = context.mLuaManager, windowManager](const sol::object& obj) {
if (obj == sol::nil) if (obj == sol::nil)
luaManager->addAction([windowManager] { windowManager->setConsoleSelectedObject(MWWorld::Ptr()); }); luaManager->addAction([windowManager] { windowManager->setConsoleSelectedObject(MWWorld::Ptr()); });
@ -302,6 +309,8 @@ namespace MWLua
// TODO // TODO
// api["_showMouseCursor"] = [](bool) {}; // api["_showMouseCursor"] = [](bool) {};
return LuaUtil::makeReadOnly(api); sol::state_view& lua = context.mLua->sol();
lua["openmw_ui"] = LuaUtil::makeReadOnly(api);
return lua["openmw_ui"];
} }
} }

@ -0,0 +1,215 @@
#include "worldbindings.hpp"
#include <components/esm3/loadacti.hpp>
#include <components/esm3/loadalch.hpp>
#include <components/esm3/loadarmo.hpp>
#include <components/esm3/loadbook.hpp>
#include <components/esm3/loadclot.hpp>
#include <components/esm3/loadmisc.hpp>
#include <components/esm3/loadskil.hpp>
#include <components/esm3/loadweap.hpp>
#include <components/lua/luastate.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/action.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/manualref.hpp"
#include "../mwworld/store.hpp"
#include "../mwworld/worldmodel.hpp"
#include "luamanagerimp.hpp"
#include "corebindings.hpp"
#include "mwscriptbindings.hpp"
namespace MWLua
{
struct CellsStore
{
};
}
namespace sol
{
template <>
struct is_automagical<MWLua::CellsStore> : std::false_type
{
};
}
namespace MWLua
{
static void checkGameInitialized(LuaUtil::LuaState* lua)
{
if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame)
throw std::runtime_error(
"This function cannot be used until the game is fully initialized.\n" + lua->debugTraceback());
}
static void addWorldTimeBindings(sol::table& api, const Context& context)
{
MWWorld::DateTimeManager* timeManager = MWBase::Environment::get().getWorld()->getTimeManager();
api["setGameTimeScale"] = [timeManager](double scale) { timeManager->setGameTimeScale(scale); };
api["setSimulationTimeScale"] = [context, timeManager](float scale) {
context.mLuaManager->addAction([scale, timeManager] { timeManager->setSimulationTimeScale(scale); });
};
api["pause"]
= [timeManager](sol::optional<std::string_view> tag) { timeManager->pause(tag.value_or("paused")); };
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 void addCellGetters(sol::table& api, const Context& context)
{
api["getCellByName"] = [](std::string_view name) {
return GCell{ &MWBase::Environment::get().getWorldModel()->getCell(name, /*forceLoad=*/false) };
};
api["getExteriorCell"] = [](int x, int y, sol::object cellOrName) {
ESM::RefId worldspace;
if (cellOrName.is<GCell>())
worldspace = cellOrName.as<GCell>().mStore->getCell()->getWorldSpace();
else if (cellOrName.is<std::string_view>() && !cellOrName.as<std::string_view>().empty())
worldspace = MWBase::Environment::get()
.getWorldModel()
->getCell(cellOrName.as<std::string_view>())
.getCell()
->getWorldSpace();
else
worldspace = ESM::Cell::sDefaultWorldspaceId;
return GCell{ &MWBase::Environment::get().getWorldModel()->getExterior(
ESM::ExteriorCellLocation(x, y, worldspace), /*forceLoad=*/false) };
};
const MWWorld::Store<ESM::Cell>* cells3Store = &MWBase::Environment::get().getESMStore()->get<ESM::Cell>();
const MWWorld::Store<ESM4::Cell>* cells4Store = &MWBase::Environment::get().getESMStore()->get<ESM4::Cell>();
sol::usertype<CellsStore> cells = context.mLua->sol().new_usertype<CellsStore>("Cells");
cells[sol::meta_function::length]
= [cells3Store, cells4Store](const CellsStore&) { return cells3Store->getSize() + cells4Store->getSize(); };
cells[sol::meta_function::index]
= [cells3Store, cells4Store](const CellsStore&, size_t index) -> sol::optional<GCell> {
if (index > cells3Store->getSize() + cells3Store->getSize() || index == 0)
return sol::nullopt;
index--; // Translate from Lua's 1-based indexing.
if (index < cells3Store->getSize())
{
const ESM::Cell* cellRecord = cells3Store->at(index);
return GCell{ &MWBase::Environment::get().getWorldModel()->getCell(
cellRecord->mId, /*forceLoad=*/false) };
}
else
{
const ESM4::Cell* cellRecord = cells4Store->at(index - cells3Store->getSize());
return GCell{ &MWBase::Environment::get().getWorldModel()->getCell(
cellRecord->mId, /*forceLoad=*/false) };
}
};
cells[sol::meta_function::pairs] = context.mLua->sol()["ipairsForArray"].template get<sol::function>();
cells[sol::meta_function::ipairs] = context.mLua->sol()["ipairsForArray"].template get<sol::function>();
api["cells"] = CellsStore{};
}
sol::table initWorldPackage(const Context& context)
{
sol::table api(context.mLua->sol(), sol::create);
addCoreTimeBindings(api, context);
addWorldTimeBindings(api, context);
addCellGetters(api, context);
api["mwscript"] = initMWScriptBindings(context);
ObjectLists* objectLists = context.mObjectLists;
api["activeActors"] = GObjectList{ objectLists->getActorsInScene() };
api["players"] = GObjectList{ objectLists->getPlayers() };
api["createObject"] = [lua = context.mLua](std::string_view recordId, sol::optional<int> count) -> GObject {
checkGameInitialized(lua);
MWWorld::ManualRef mref(*MWBase::Environment::get().getESMStore(), ESM::RefId::deserializeText(recordId));
const MWWorld::Ptr& ptr = mref.getPtr();
ptr.getRefData().disable();
MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getDraftCell();
MWWorld::Ptr newPtr = ptr.getClass().copyToCell(ptr, cell, count.value_or(1));
return GObject(newPtr);
};
api["getObjectByFormId"] = [](std::string_view formIdStr) -> GObject {
ESM::RefId refId = ESM::RefId::deserializeText(formIdStr);
if (!refId.is<ESM::FormId>())
throw std::runtime_error("FormId expected, got " + std::string(formIdStr) + "; use core.getFormId");
return GObject(*refId.getIf<ESM::FormId>());
};
// Creates a new record in the world database.
api["createRecord"] = sol::overload(
[lua = context.mLua](const ESM::Activator& activator) -> const ESM::Activator* {
checkGameInitialized(lua);
return MWBase::Environment::get().getESMStore()->insert(activator);
},
[lua = context.mLua](const ESM::Armor& armor) -> const ESM::Armor* {
checkGameInitialized(lua);
return MWBase::Environment::get().getESMStore()->insert(armor);
},
[lua = context.mLua](const ESM::Clothing& clothing) -> const ESM::Clothing* {
checkGameInitialized(lua);
return MWBase::Environment::get().getESMStore()->insert(clothing);
},
[lua = context.mLua](const ESM::Book& book) -> const ESM::Book* {
checkGameInitialized(lua);
return MWBase::Environment::get().getESMStore()->insert(book);
},
[lua = context.mLua](const ESM::Miscellaneous& misc) -> const ESM::Miscellaneous* {
checkGameInitialized(lua);
return MWBase::Environment::get().getESMStore()->insert(misc);
},
[lua = context.mLua](const ESM::Potion& potion) -> const ESM::Potion* {
checkGameInitialized(lua);
return MWBase::Environment::get().getESMStore()->insert(potion);
},
[lua = context.mLua](const ESM::Weapon& weapon) -> const ESM::Weapon* {
checkGameInitialized(lua);
return MWBase::Environment::get().getESMStore()->insert(weapon);
});
api["_runStandardActivationAction"] = [context](const GObject& object, const GObject& actor) {
if (!object.ptr().getRefData().activate())
return;
context.mLuaManager->addAction(
[object, actor] {
const MWWorld::Ptr& objPtr = object.ptr();
const MWWorld::Ptr& actorPtr = actor.ptr();
objPtr.getClass().activate(objPtr, actorPtr)->execute(actorPtr);
},
"_runStandardActivationAction");
};
api["_runStandardUseAction"] = [context](const GObject& object, const GObject& actor, bool force) {
context.mLuaManager->addAction(
[object, actor, force] {
const MWWorld::Ptr& actorPtr = actor.ptr();
const MWWorld::Ptr& objectPtr = object.ptr();
if (actorPtr == MWBase::Environment::get().getWorld()->getPlayerPtr())
MWBase::Environment::get().getWindowManager()->useItem(objectPtr, force);
else
{
std::unique_ptr<MWWorld::Action> action = objectPtr.getClass().use(objectPtr, force);
action->execute(actorPtr, true);
}
},
"_runStandardUseAction");
};
return LuaUtil::makeReadOnly(api);
}
}

@ -0,0 +1,13 @@
#ifndef MWLUA_WORLDBINDINGS_H
#define MWLUA_WORLDBINDINGS_H
#include <sol/forward.hpp>
#include "context.hpp"
namespace MWLua
{
sol::table initWorldPackage(const Context&);
}
#endif // MWLUA_WORLDBINDINGS_H

@ -666,6 +666,18 @@ void MWState::StateManager::update(float duration)
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu);
} }
} }
if (mNewGameRequest)
{
newGame();
mNewGameRequest = false;
}
if (mLoadRequest)
{
loadGame(*mLoadRequest);
mLoadRequest = std::nullopt;
}
} }
bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const

@ -14,6 +14,8 @@ namespace MWState
{ {
bool mQuitRequest; bool mQuitRequest;
bool mAskLoadRecent; bool mAskLoadRecent;
bool mNewGameRequest = false;
std::optional<std::filesystem::path> mLoadRequest;
State mState; State mState;
CharacterManager mCharacterManager; CharacterManager mCharacterManager;
double mTimePlayed; double mTimePlayed;
@ -36,6 +38,9 @@ namespace MWState
void askLoadRecent() override; void askLoadRecent() override;
void requestNewGame() override { mNewGameRequest = true; }
void requestLoad(const std::filesystem::path& filepath) override { mLoadRequest = filepath; }
State getState() const override; State getState() const override;
void newGame(bool bypass = false) override; void newGame(bool bypass = false) override;

@ -20,8 +20,10 @@ namespace ESM
static constexpr Flags sCustom = 1ull << 1; // local; can be attached/detached by a global script static constexpr Flags sCustom = 1ull << 1; // local; can be attached/detached by a global script
static constexpr Flags sPlayer = 1ull << 2; // auto attach to players static constexpr Flags sPlayer = 1ull << 2; // auto attach to players
static constexpr Flags sMerge = 1ull // merge with configuration for this script from previous content files.
<< 3; // merge with configuration for this script from previous content files. static constexpr Flags sMerge = 1ull << 3;
static constexpr Flags sMenu = 1ull << 4; // start as a menu script
std::string mScriptPath; // VFS path to the script. std::string mScriptPath; // VFS path to the script.
std::string mInitializationData; // Serialized Lua table. It is a binary data. Can contain '\0'. std::string mInitializationData; // Serialized Lua table. It is a binary data. Can contain '\0'.

@ -18,6 +18,7 @@ namespace LuaUtil
{ "GLOBAL", ESM::LuaScriptCfg::sGlobal }, { "GLOBAL", ESM::LuaScriptCfg::sGlobal },
{ "CUSTOM", ESM::LuaScriptCfg::sCustom }, { "CUSTOM", ESM::LuaScriptCfg::sCustom },
{ "PLAYER", ESM::LuaScriptCfg::sPlayer }, { "PLAYER", ESM::LuaScriptCfg::sPlayer },
{ "MENU", ESM::LuaScriptCfg::sMenu },
}; };
const std::map<std::string, ESM::RecNameInts, std::less<>> typeTagsByName{ const std::map<std::string, ESM::RecNameInts, std::less<>> typeTagsByName{

@ -22,6 +22,7 @@ namespace LuaUtil
std::optional<int> findId(std::string_view path) const; std::optional<int> findId(std::string_view path) const;
bool isCustomScript(int id) const { return mScripts[id].mFlags & ESM::LuaScriptCfg::sCustom; } bool isCustomScript(int id) const { return mScripts[id].mFlags & ESM::LuaScriptCfg::sCustom; }
ScriptIdsWithInitializationData getMenuConf() const { return getConfByFlag(ESM::LuaScriptCfg::sMenu); }
ScriptIdsWithInitializationData getGlobalConf() const { return getConfByFlag(ESM::LuaScriptCfg::sGlobal); } ScriptIdsWithInitializationData getGlobalConf() const { return getConfByFlag(ESM::LuaScriptCfg::sGlobal); }
ScriptIdsWithInitializationData getPlayerConf() const { return getConfByFlag(ESM::LuaScriptCfg::sPlayer); } ScriptIdsWithInitializationData getPlayerConf() const { return getConfByFlag(ESM::LuaScriptCfg::sPlayer); }
ScriptIdsWithInitializationData getLocalConf( ScriptIdsWithInitializationData getLocalConf(

@ -49,6 +49,14 @@ namespace LuaUtil
return !valid; return !valid;
}), }),
mCallbacks.end()); mCallbacks.end());
mPermanentCallbacks.erase(std::remove_if(mPermanentCallbacks.begin(), mPermanentCallbacks.end(),
[&](const Callback& callback) {
bool valid = callback.isValid();
if (valid)
callback.tryCall(mSectionName, changedKey);
return !valid;
}),
mPermanentCallbacks.end());
mStorage->mRunningCallbacks.erase(this); mStorage->mRunningCallbacks.erase(this);
} }
@ -112,7 +120,8 @@ namespace LuaUtil
}; };
sview["asTable"] = [](const SectionView& section) { return section.mSection->asTable(); }; sview["asTable"] = [](const SectionView& section) { return section.mSection->asTable(); };
sview["subscribe"] = [](const SectionView& section, const sol::table& callback) { sview["subscribe"] = [](const SectionView& section, const sol::table& callback) {
std::vector<Callback>& callbacks = section.mSection->mCallbacks; std::vector<Callback>& callbacks
= section.mForMenuScripts ? section.mSection->mPermanentCallbacks : section.mSection->mCallbacks;
if (!callbacks.empty() && callbacks.size() == callbacks.capacity()) if (!callbacks.empty() && callbacks.size() == callbacks.capacity())
{ {
callbacks.erase( callbacks.erase(
@ -166,6 +175,16 @@ namespace LuaUtil
return LuaUtil::makeReadOnly(res); return LuaUtil::makeReadOnly(res);
} }
sol::table LuaStorage::initMenuPackage(lua_State* lua, LuaStorage* playerStorage)
{
sol::table res(lua, sol::create);
res["playerSection"] = [playerStorage](std::string_view section) {
return playerStorage->getMutableSection(section, /*forMenuScripts=*/true);
};
res["allPlayerSections"] = [playerStorage]() { return playerStorage->getAllSections(); };
return LuaUtil::makeReadOnly(res);
}
void LuaStorage::clearTemporaryAndRemoveCallbacks() void LuaStorage::clearTemporaryAndRemoveCallbacks()
{ {
auto it = mData.begin(); auto it = mData.begin();
@ -174,6 +193,7 @@ namespace LuaUtil
it->second->mCallbacks.clear(); it->second->mCallbacks.clear();
if (!it->second->mPermanent) if (!it->second->mPermanent)
{ {
it->second->mPermanentCallbacks.clear();
it->second->mValues.clear(); it->second->mValues.clear();
it = mData.erase(it); it = mData.erase(it);
} }
@ -231,10 +251,10 @@ namespace LuaUtil
return newIt->second; return newIt->second;
} }
sol::object LuaStorage::getSection(std::string_view sectionName, bool readOnly) sol::object LuaStorage::getSection(std::string_view sectionName, bool readOnly, bool forMenuScripts)
{ {
const std::shared_ptr<Section>& section = getSection(sectionName); const std::shared_ptr<Section>& section = getSection(sectionName);
return sol::make_object<SectionView>(mLua, SectionView{ section, readOnly }); return sol::make_object<SectionView>(mLua, SectionView{ section, readOnly, forMenuScripts });
} }
sol::table LuaStorage::getAllSections(bool readOnly) sol::table LuaStorage::getAllSections(bool readOnly)

@ -17,6 +17,7 @@ namespace LuaUtil
static sol::table initGlobalPackage(lua_State* lua, LuaStorage* globalStorage); static sol::table initGlobalPackage(lua_State* lua, LuaStorage* globalStorage);
static sol::table initLocalPackage(lua_State* lua, LuaStorage* globalStorage); static sol::table initLocalPackage(lua_State* lua, LuaStorage* globalStorage);
static sol::table initPlayerPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage); static sol::table initPlayerPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage);
static sol::table initMenuPackage(lua_State* lua, LuaStorage* playerStorage);
explicit LuaStorage(lua_State* lua) explicit LuaStorage(lua_State* lua)
: mLua(lua) : mLua(lua)
@ -27,8 +28,11 @@ namespace LuaUtil
void load(const std::filesystem::path& path); void load(const std::filesystem::path& path);
void save(const std::filesystem::path& path) const; void save(const std::filesystem::path& path) const;
sol::object getSection(std::string_view sectionName, bool readOnly); sol::object getSection(std::string_view sectionName, bool readOnly, bool forMenuScripts = false);
sol::object getMutableSection(std::string_view sectionName) { return getSection(sectionName, false); } sol::object getMutableSection(std::string_view sectionName, bool forMenuScripts = false)
{
return getSection(sectionName, false, forMenuScripts);
}
sol::object getReadOnlySection(std::string_view sectionName) { return getSection(sectionName, true); } sol::object getReadOnlySection(std::string_view sectionName) { return getSection(sectionName, true); }
sol::table getAllSections(bool readOnly = false); sol::table getAllSections(bool readOnly = false);
@ -87,6 +91,7 @@ namespace LuaUtil
std::string mSectionName; std::string mSectionName;
std::map<std::string, Value, std::less<>> mValues; std::map<std::string, Value, std::less<>> mValues;
std::vector<Callback> mCallbacks; std::vector<Callback> mCallbacks;
std::vector<Callback> mPermanentCallbacks;
bool mPermanent = true; bool mPermanent = true;
static Value sEmpty; static Value sEmpty;
}; };
@ -94,6 +99,7 @@ namespace LuaUtil
{ {
std::shared_ptr<Section> mSection; std::shared_ptr<Section> mSection;
bool mReadOnly; bool mReadOnly;
bool mForMenuScripts = false;
}; };
const std::shared_ptr<Section>& getSection(std::string_view sectionName); const std::shared_ptr<Section>& getSection(std::string_view sectionName);

@ -72,9 +72,10 @@ set(BUILTIN_DATA_FILES
scripts/omw/camera/settings.lua scripts/omw/camera/settings.lua
scripts/omw/camera/move360.lua scripts/omw/camera/move360.lua
scripts/omw/camera/first_person_auto_switch.lua scripts/omw/camera/first_person_auto_switch.lua
scripts/omw/console/player.lua
scripts/omw/console/global.lua scripts/omw/console/global.lua
scripts/omw/console/local.lua scripts/omw/console/local.lua
scripts/omw/console/player.lua
scripts/omw/console/menu.lua
scripts/omw/mechanics/playercontroller.lua scripts/omw/mechanics/playercontroller.lua
scripts/omw/playercontrols.lua scripts/omw/playercontrols.lua
scripts/omw/settings/player.lua scripts/omw/settings/player.lua

@ -19,6 +19,7 @@ NPC,CREATURE: scripts/omw/ai.lua
PLAYER: scripts/omw/ui.lua PLAYER: scripts/omw/ui.lua
# Lua console # Lua console
MENU: scripts/omw/console/menu.lua
PLAYER: scripts/omw/console/player.lua PLAYER: scripts/omw/console/player.lua
GLOBAL: scripts/omw/console/global.lua GLOBAL: scripts/omw/console/global.lua
CUSTOM: scripts/omw/console/local.lua CUSTOM: scripts/omw/console/local.lua

@ -0,0 +1,114 @@
local menu = require('openmw.menu')
local ui = require('openmw.ui')
local util = require('openmw.util')
local menuModeName = 'Lua[Menu]'
local function printHelp()
local msg = [[
This is the built-in Lua interpreter.
help() - print this message
exit() - exit Lua mode
view(_G) - print content of the table `_G` (current environment)
standard libraries (math, string, etc.) are loaded by default but not visible in `_G`
view(menu, 2) - print table `menu` (i.e. `openmw.menu`) and its subtables (2 - traversal depth)]]
ui.printToConsole(msg, ui.CONSOLE_COLOR.Info)
end
local function printToConsole(...)
local strs = {}
for i = 1, select('#', ...) do
strs[i] = tostring(select(i, ...))
end
return ui.printToConsole(table.concat(strs, '\t'), ui.CONSOLE_COLOR.Info)
end
local function printRes(...)
if select('#', ...) >= 0 then
printToConsole(...)
end
end
local function exitLuaMenuMode()
ui.setConsoleMode('')
ui.printToConsole('Lua mode OFF', ui.CONSOLE_COLOR.Success)
end
local function enterLuaMenuMode()
ui.printToConsole('Lua mode ON, use exit() to return, help() for more info', ui.CONSOLE_COLOR.Success)
ui.printToConsole('Context: Menu', ui.CONSOLE_COLOR.Success)
ui.setConsoleMode(menuModeName)
end
local env = {
I = require('openmw.interfaces'),
menu = require('openmw.menu'),
util = require('openmw.util'),
core = require('openmw.core'),
storage = require('openmw.storage'),
vfs = require('openmw.vfs'),
ambient = require('openmw.ambient'),
async = require('openmw.async'),
ui = require('openmw.ui'),
aux_util = require('openmw_aux.util'),
view = require('openmw_aux.util').deepToString,
print = printToConsole,
exit = exitLuaMenuMode,
help = printHelp,
}
env._G = env
setmetatable(env, {__index = _G, __metatable = false})
_G = nil
local function executeLuaCode(code)
local fn
local ok, err = pcall(function() fn = util.loadCode('return ' .. code, env) end)
if ok then
ok, err = pcall(function() printRes(fn()) end)
else
ok, err = pcall(function() util.loadCode(code, env)() end)
end
if not ok then
ui.printToConsole(err, ui.CONSOLE_COLOR.Error)
end
end
local usageInfo = [[
Usage: 'lua menu' or 'luam' - enter menu context
Other contexts are available only when the game is started:
'lua player' or 'luap' - enter player context
'lua global' or 'luag' - enter global context
'lua selected' or 'luas' - enter local context on the selected object]]
local function onConsoleCommand(mode, cmd)
if mode == '' then
cmd, arg = cmd:lower():match('(%w+) *(%w*)')
if (cmd == 'lua' and arg == 'menu') or cmd == 'luam' then
enterLuaMenuMode()
elseif menu.getState() == menu.STATE.NoGame and (cmd == 'lua' or cmd == 'luap' or cmd == 'luas' or cmd == 'luag') then
ui.printToConsole(usageInfo, ui.CONSOLE_COLOR.Info)
end
elseif mode == menuModeName then
if cmd == 'exit()' then
exitLuaMenuMode()
else
executeLuaCode(cmd)
end
end
end
local function onStateChanged()
local mode = ui.getConsoleMode()
if menu.getState() ~= menu.STATE.Ended and mode ~= menuModeName then
-- When a new game started or loaded reset console mode (except of `luam`) because
-- other modes become invalid after restarting Lua scripts.
ui.setConsoleMode('')
end
end
return {
engineHandlers = {
onConsoleCommand = onConsoleCommand,
onStateChanged = onStateChanged,
},
}

@ -77,6 +77,7 @@ local env = {
nearby = require('openmw.nearby'), nearby = require('openmw.nearby'),
self = require('openmw.self'), self = require('openmw.self'),
input = require('openmw.input'), input = require('openmw.input'),
postprocessing = require('openmw.postprocessing'),
ui = require('openmw.ui'), ui = require('openmw.ui'),
camera = require('openmw.camera'), camera = require('openmw.camera'),
aux_util = require('openmw_aux.util'), aux_util = require('openmw_aux.util'),
@ -114,9 +115,12 @@ local function onConsoleCommand(mode, cmd, selectedObject)
cmd = 'luag' cmd = 'luag'
elseif arg == 'selected' then elseif arg == 'selected' then
cmd = 'luas' cmd = 'luas'
elseif arg == 'menu' then
-- handled in menu.lua
else else
local msg = [[ local msg = [[
Usage: 'lua player' or 'luap' - enter player context Usage: 'lua menu' or 'luam' - enter menu context
'lua player' or 'luap' - enter player context
'lua global' or 'luag' - enter global context 'lua global' or 'luag' - enter global context
'lua selected' or 'luas' - enter local context on the selected object]] 'lua selected' or 'luas' - enter local context on the selected object]]
ui.printToConsole(msg, ui.CONSOLE_COLOR.Info) ui.printToConsole(msg, ui.CONSOLE_COLOR.Info)
@ -158,4 +162,3 @@ return {
OMWConsoleHelp = printHelp, OMWConsoleHelp = printHelp,
} }
} }

Loading…
Cancel
Save