mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-02-04 03:40:14 +00:00
Merge branch 'menuscripts' into 'master'
Add new Lua context: menu scripts Closes #7805 and #7648 See merge request OpenMW/openmw!3464
This commit is contained in:
commit
1338e884a9
@ -174,11 +174,13 @@
|
||||
Feature #7618: Show the player character's health in the save details
|
||||
Feature #7625: Add some missing console error outputs
|
||||
Feature #7634: Support NiParticleBomb
|
||||
Feature #7648: Lua Save game API
|
||||
Feature #7652: Sort inactive post processing shaders list properly
|
||||
Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore
|
||||
Feature #7709: Improve resolution selection in Launcher
|
||||
Feature #7792: Support Timescale Clouds
|
||||
Feature #7795: Support MaxNumberRipples INI setting
|
||||
Feature #7805: Lua Menu context
|
||||
Task #5896: Do not use deprecated MyGUI properties
|
||||
Task #6624: Drop support for saves made prior to 0.45
|
||||
Task #7113: Move from std::atoi to std::from_char
|
||||
|
@ -72,7 +72,7 @@ message(STATUS "Configuring OpenMW...")
|
||||
set(OPENMW_VERSION_MAJOR 0)
|
||||
set(OPENMW_VERSION_MINOR 49)
|
||||
set(OPENMW_VERSION_RELEASE 0)
|
||||
set(OPENMW_LUA_API_REVISION 51)
|
||||
set(OPENMW_LUA_API_REVISION 52)
|
||||
set(OPENMW_POSTPROCESSING_API_REVISION 1)
|
||||
|
||||
set(OPENMW_VERSION_COMMITHASH "")
|
||||
|
@ -61,10 +61,14 @@ add_openmw_dir (mwscript
|
||||
|
||||
add_openmw_dir (mwlua
|
||||
luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant
|
||||
context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings
|
||||
camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings itemdata
|
||||
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
|
||||
worker magicbindings factionbindings classbindings animationbindings
|
||||
context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings
|
||||
mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings
|
||||
postprocessingbindings stats debugbindings corebindings worldbindings worker magicbindings factionbindings
|
||||
classbindings itemdata inputprocessor animationbindings
|
||||
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
|
||||
|
@ -890,8 +890,8 @@ void OMW::Engine::prepareEngine()
|
||||
<< 100 * static_cast<double>(result.second) / result.first << "%)";
|
||||
}
|
||||
|
||||
mLuaManager->init();
|
||||
mLuaManager->loadPermanentStorage(mCfgMgr.getUserConfigPath());
|
||||
mLuaManager->init();
|
||||
|
||||
// starts a separate lua thread if "lua num threads" > 0
|
||||
mLuaWorker = std::make_unique<MWLua::Worker>(*mLuaManager, *mViewer);
|
||||
|
@ -55,6 +55,7 @@ namespace MWBase
|
||||
|
||||
virtual void newGameStarted() = 0;
|
||||
virtual void gameLoaded() = 0;
|
||||
virtual void gameEnded() = 0;
|
||||
virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0;
|
||||
virtual void objectRemovedFromScene(const MWWorld::Ptr& ptr) = 0;
|
||||
virtual void objectTeleported(const MWWorld::Ptr& ptr) = 0;
|
||||
|
@ -44,6 +44,9 @@ namespace MWBase
|
||||
|
||||
virtual void askLoadRecent() = 0;
|
||||
|
||||
virtual void requestNewGame() = 0;
|
||||
virtual void requestLoad(const std::filesystem::path& filepath) = 0;
|
||||
|
||||
virtual State getState() const = 0;
|
||||
|
||||
virtual void newGame(bool bypass = false) = 0;
|
||||
|
@ -166,7 +166,8 @@ namespace MWBase
|
||||
|
||||
virtual void setConsoleSelectedObject(const MWWorld::Ptr& object) = 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_Error = "#FF2222";
|
||||
|
@ -51,6 +51,7 @@
|
||||
#include <components/l10n/manager.hpp>
|
||||
|
||||
#include <components/lua_ui/util.hpp>
|
||||
#include <components/lua_ui/widget.hpp>
|
||||
|
||||
#include <components/settings/values.hpp>
|
||||
|
||||
@ -546,7 +547,8 @@ namespace MWGui
|
||||
{
|
||||
try
|
||||
{
|
||||
LuaUi::clearUserInterface();
|
||||
LuaUi::clearGameInterface();
|
||||
LuaUi::clearMenuInterface();
|
||||
|
||||
mStatsWatcher.reset();
|
||||
|
||||
@ -1675,7 +1677,10 @@ namespace MWGui
|
||||
|
||||
void WindowManager::onKeyFocusChanged(MyGUI::Widget* widget)
|
||||
{
|
||||
if (widget && widget->castType<MyGUI::EditBox>(false))
|
||||
bool isEditBox = widget && widget->castType<MyGUI::EditBox>(false);
|
||||
LuaUi::WidgetExtension* luaWidget = dynamic_cast<LuaUi::WidgetExtension*>(widget);
|
||||
bool capturesInput = luaWidget ? luaWidget->isTextInput() : isEditBox;
|
||||
if (widget && capturesInput)
|
||||
SDL_StartTextInput();
|
||||
else
|
||||
SDL_StopTextInput();
|
||||
@ -2173,11 +2178,16 @@ namespace MWGui
|
||||
mConsole->print(msg, color);
|
||||
}
|
||||
|
||||
void WindowManager::setConsoleMode(const std::string& mode)
|
||||
void WindowManager::setConsoleMode(std::string_view mode)
|
||||
{
|
||||
mConsole->setConsoleMode(mode);
|
||||
}
|
||||
|
||||
const std::string& WindowManager::getConsoleMode()
|
||||
{
|
||||
return mConsole->getConsoleMode();
|
||||
}
|
||||
|
||||
void WindowManager::createCursors()
|
||||
{
|
||||
MyGUI::ResourceManager::EnumeratorPtr enumerator = MyGUI::ResourceManager::getInstance().getEnumerator();
|
||||
|
@ -192,7 +192,8 @@ namespace MWGui
|
||||
void setConsoleSelectedObject(const MWWorld::Ptr& object) override;
|
||||
MWWorld::Ptr getConsoleSelectedObject() const 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)
|
||||
/// @param time time left to start drowning
|
||||
|
@ -15,6 +15,7 @@ namespace MWLua
|
||||
|
||||
struct Context
|
||||
{
|
||||
bool mIsMenu;
|
||||
bool mIsGlobal;
|
||||
LuaManager* mLuaManager;
|
||||
LuaUtil::LuaState* mLua;
|
||||
|
141
apps/openmw/mwlua/corebindings.cpp
Normal file
141
apps/openmw/mwlua/corebindings.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
#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 "animationbindings.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();
|
||||
};
|
||||
// TODO: remove in global context?
|
||||
api["getRealFrameDuration"] = []() { return MWBase::Environment::get().getFrameDuration(); };
|
||||
}
|
||||
|
||||
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["vfx"] = initCoreVfxBindings(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;
|
||||
api["vfx"] = sol::nil;
|
||||
return LuaUtil::makeReadOnly(api);
|
||||
}
|
||||
}
|
19
apps/openmw/mwlua/corebindings.hpp
Normal file
19
apps/openmw/mwlua/corebindings.hpp
Normal file
@ -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
|
||||
#define MWLUA_GLOBALSCRIPTS_H
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <components/lua/luastate.hpp>
|
||||
#include <components/lua/scriptscontainer.hpp>
|
||||
|
||||
|
@ -36,6 +36,12 @@ namespace MWLua
|
||||
|
||||
sol::table initInputPackage(const Context& context)
|
||||
{
|
||||
{
|
||||
sol::state_view& lua = context.mLua->sol();
|
||||
if (lua["openmw_input"] != sol::nil)
|
||||
return lua["openmw_input"];
|
||||
}
|
||||
|
||||
sol::usertype<SDL_Keysym> keyEvent = context.mLua->sol().new_usertype<SDL_Keysym>("KeyEvent");
|
||||
keyEvent["symbol"] = sol::readonly_property([](const SDL_Keysym& e) {
|
||||
if (e.sym > 0 && e.sym <= 255)
|
||||
@ -424,7 +430,9 @@ namespace MWLua
|
||||
{ "Tab", SDL_SCANCODE_TAB },
|
||||
}));
|
||||
|
||||
return LuaUtil::makeReadOnly(api);
|
||||
sol::state_view& lua = context.mLua->sol();
|
||||
lua["openmw_input"] = LuaUtil::makeReadOnly(api);
|
||||
return lua["openmw_input"];
|
||||
}
|
||||
|
||||
}
|
||||
|
72
apps/openmw/mwlua/inputprocessor.hpp
Normal file
72
apps/openmw/mwlua/inputprocessor.hpp
Normal file
@ -0,0 +1,72 @@
|
||||
#ifndef MWLUA_INPUTPROCESSOR_H
|
||||
#define MWLUA_INPUTPROCESSOR_H
|
||||
|
||||
#include <SDL_events.h>
|
||||
|
||||
#include <components/sdlutil/events.hpp>
|
||||
|
||||
#include "../mwbase/luamanager.hpp"
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
template <class Container>
|
||||
class InputProcessor
|
||||
{
|
||||
public:
|
||||
InputProcessor(Container* scriptsContainer)
|
||||
: mScriptsContainer(scriptsContainer)
|
||||
{
|
||||
mScriptsContainer->registerEngineHandlers({ &mKeyPressHandlers, &mKeyReleaseHandlers,
|
||||
&mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mTouchpadPressed,
|
||||
&mTouchpadReleased, &mTouchpadMoved });
|
||||
}
|
||||
|
||||
void processInputEvent(const MWBase::LuaManager::InputEvent& event)
|
||||
{
|
||||
using InputEvent = MWBase::LuaManager::InputEvent;
|
||||
switch (event.mType)
|
||||
{
|
||||
case InputEvent::KeyPressed:
|
||||
mScriptsContainer->callEngineHandlers(mKeyPressHandlers, std::get<SDL_Keysym>(event.mValue));
|
||||
break;
|
||||
case InputEvent::KeyReleased:
|
||||
mScriptsContainer->callEngineHandlers(mKeyReleaseHandlers, std::get<SDL_Keysym>(event.mValue));
|
||||
break;
|
||||
case InputEvent::ControllerPressed:
|
||||
mScriptsContainer->callEngineHandlers(mControllerButtonPressHandlers, std::get<int>(event.mValue));
|
||||
break;
|
||||
case InputEvent::ControllerReleased:
|
||||
mScriptsContainer->callEngineHandlers(
|
||||
mControllerButtonReleaseHandlers, std::get<int>(event.mValue));
|
||||
break;
|
||||
case InputEvent::Action:
|
||||
mScriptsContainer->callEngineHandlers(mActionHandlers, std::get<int>(event.mValue));
|
||||
break;
|
||||
case InputEvent::TouchPressed:
|
||||
mScriptsContainer->callEngineHandlers(
|
||||
mTouchpadPressed, std::get<SDLUtil::TouchEvent>(event.mValue));
|
||||
break;
|
||||
case InputEvent::TouchReleased:
|
||||
mScriptsContainer->callEngineHandlers(
|
||||
mTouchpadReleased, std::get<SDLUtil::TouchEvent>(event.mValue));
|
||||
break;
|
||||
case InputEvent::TouchMoved:
|
||||
mScriptsContainer->callEngineHandlers(mTouchpadMoved, std::get<SDLUtil::TouchEvent>(event.mValue));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Container* mScriptsContainer;
|
||||
typename Container::EngineHandlerList mKeyPressHandlers{ "onKeyPress" };
|
||||
typename Container::EngineHandlerList mKeyReleaseHandlers{ "onKeyRelease" };
|
||||
typename Container::EngineHandlerList mControllerButtonPressHandlers{ "onControllerButtonPress" };
|
||||
typename Container::EngineHandlerList mControllerButtonReleaseHandlers{ "onControllerButtonRelease" };
|
||||
typename Container::EngineHandlerList mActionHandlers{ "onInputAction" };
|
||||
typename Container::EngineHandlerList mTouchpadPressed{ "onTouchPress" };
|
||||
typename Container::EngineHandlerList mTouchpadReleased{ "onTouchRelease" };
|
||||
typename Container::EngineHandlerList mTouchpadMoved{ "onTouchMove" };
|
||||
};
|
||||
}
|
||||
|
||||
#endif // MWLUA_INPUTPROCESSOR_H
|
@ -1,332 +1,31 @@
|
||||
#include "luabindings.hpp"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#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/asyncpackage.hpp>
|
||||
#include <components/lua/utilpackage.hpp>
|
||||
#include <components/misc/strings/lower.hpp>
|
||||
#include <components/version/version.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 "luaevents.hpp"
|
||||
#include "luamanagerimp.hpp"
|
||||
#include "mwscriptbindings.hpp"
|
||||
#include "objectlists.hpp"
|
||||
|
||||
#include "animationbindings.hpp"
|
||||
#include "camerabindings.hpp"
|
||||
#include "cellbindings.hpp"
|
||||
#include "corebindings.hpp"
|
||||
#include "debugbindings.hpp"
|
||||
#include "factionbindings.hpp"
|
||||
#include "inputbindings.hpp"
|
||||
#include "magicbindings.hpp"
|
||||
#include "localscripts.hpp"
|
||||
#include "menuscripts.hpp"
|
||||
#include "nearbybindings.hpp"
|
||||
#include "objectbindings.hpp"
|
||||
#include "postprocessingbindings.hpp"
|
||||
#include "soundbindings.hpp"
|
||||
#include "stats.hpp"
|
||||
#include "types/types.hpp"
|
||||
#include "uibindings.hpp"
|
||||
#include "vfsbindings.hpp"
|
||||
#include "worldbindings.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 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();
|
||||
};
|
||||
api["getRealFrameDuration"] = []() { return MWBase::Environment::get().getFrameDuration(); };
|
||||
|
||||
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["vfx"] = initCoreVfxBindings(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)
|
||||
{
|
||||
sol::state_view lua = context.mLua->sol();
|
||||
@ -336,8 +35,6 @@ namespace MWLua
|
||||
{ "openmw.async",
|
||||
LuaUtil::getAsyncPackageInitializer(
|
||||
lua, [tm] { return tm->getSimulationTime(); }, [tm] { return tm->getGameTime(); }) },
|
||||
{ "openmw.core", initCorePackage(context) },
|
||||
{ "openmw.types", initTypesPackage(context) },
|
||||
{ "openmw.util", LuaUtil::initUtilPackage(lua) },
|
||||
{ "openmw.vfs", initVFSPackage(context) },
|
||||
};
|
||||
@ -348,6 +45,8 @@ namespace MWLua
|
||||
initObjectBindingsForGlobalScripts(context);
|
||||
initCellBindingsForGlobalScripts(context);
|
||||
return {
|
||||
{ "openmw.core", initCorePackage(context) },
|
||||
{ "openmw.types", initTypesPackage(context) },
|
||||
{ "openmw.world", initWorldPackage(context) },
|
||||
};
|
||||
}
|
||||
@ -358,6 +57,8 @@ namespace MWLua
|
||||
initCellBindingsForLocalScripts(context);
|
||||
LocalScripts::initializeSelfPackage(context);
|
||||
return {
|
||||
{ "openmw.core", initCorePackage(context) },
|
||||
{ "openmw.types", initTypesPackage(context) },
|
||||
{ "openmw.nearby", initNearbyPackage(context) },
|
||||
};
|
||||
}
|
||||
@ -374,4 +75,14 @@ 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) },
|
||||
{ "openmw.input", initInputPackage(context) },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -12,14 +12,18 @@ namespace MWLua
|
||||
// Initialize Lua packages that are available for all scripts.
|
||||
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&);
|
||||
|
||||
// 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&);
|
||||
|
||||
// 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&);
|
||||
|
||||
// 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
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include "globalscripts.hpp"
|
||||
#include "localscripts.hpp"
|
||||
#include "menuscripts.hpp"
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
@ -23,6 +24,7 @@ namespace MWLua
|
||||
mLocalEventBatch.clear();
|
||||
mNewGlobalEventBatch.clear();
|
||||
mNewLocalEventBatch.clear();
|
||||
mMenuEvents.clear();
|
||||
}
|
||||
|
||||
void LuaEvents::finalizeEventBatch()
|
||||
@ -51,6 +53,13 @@ namespace MWLua
|
||||
mLocalEventBatch.clear();
|
||||
}
|
||||
|
||||
void LuaEvents::callMenuEventHandlers()
|
||||
{
|
||||
for (const Global& e : mMenuEvents)
|
||||
mMenuScripts.receiveEvent(e.mEventName, e.mEventData);
|
||||
mMenuEvents.clear();
|
||||
}
|
||||
|
||||
template <typename Event>
|
||||
static void saveEvent(ESM::ESMWriter& esm, ESM::RefNum dest, const Event& event)
|
||||
{
|
||||
|
@ -23,12 +23,14 @@ namespace MWLua
|
||||
{
|
||||
|
||||
class GlobalScripts;
|
||||
class MenuScripts;
|
||||
|
||||
class LuaEvents
|
||||
{
|
||||
public:
|
||||
explicit LuaEvents(GlobalScripts& globalScripts)
|
||||
explicit LuaEvents(GlobalScripts& globalScripts, MenuScripts& menuScripts)
|
||||
: mGlobalScripts(globalScripts)
|
||||
, mMenuScripts(menuScripts)
|
||||
{
|
||||
}
|
||||
|
||||
@ -45,11 +47,13 @@ namespace MWLua
|
||||
};
|
||||
|
||||
void addGlobalEvent(Global event) { mNewGlobalEventBatch.push_back(std::move(event)); }
|
||||
void addMenuEvent(Global event) { mMenuEvents.push_back(std::move(event)); }
|
||||
void addLocalEvent(Local event) { mNewLocalEventBatch.push_back(std::move(event)); }
|
||||
|
||||
void clear();
|
||||
void finalizeEventBatch();
|
||||
void callEventHandlers();
|
||||
void callMenuEventHandlers();
|
||||
|
||||
void load(lua_State* lua, ESM::ESMReader& esm, const std::map<int, int>& contentFileMapping,
|
||||
const LuaUtil::UserdataSerializer* serializer);
|
||||
@ -57,10 +61,12 @@ namespace MWLua
|
||||
|
||||
private:
|
||||
GlobalScripts& mGlobalScripts;
|
||||
MenuScripts& mMenuScripts;
|
||||
std::vector<Global> mNewGlobalEventBatch;
|
||||
std::vector<Local> mNewLocalEventBatch;
|
||||
std::vector<Global> mGlobalEventBatch;
|
||||
std::vector<Local> mLocalEventBatch;
|
||||
std::vector<Global> mMenuEvents;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <components/l10n/manager.hpp>
|
||||
|
||||
#include <components/lua_ui/content.hpp>
|
||||
#include <components/lua_ui/registerscriptsettings.hpp>
|
||||
#include <components/lua_ui/util.hpp>
|
||||
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
@ -65,18 +66,25 @@ namespace MWLua
|
||||
mGlobalScripts.setSerializer(mGlobalSerializer.get());
|
||||
}
|
||||
|
||||
LuaManager::~LuaManager()
|
||||
{
|
||||
LuaUi::clearSettings();
|
||||
}
|
||||
|
||||
void LuaManager::initConfiguration()
|
||||
{
|
||||
mConfiguration.init(MWBase::Environment::get().getESMStore()->getLuaScriptsCfg());
|
||||
Log(Debug::Verbose) << "Lua scripts configuration (" << mConfiguration.size() << " scripts):";
|
||||
for (size_t i = 0; i < mConfiguration.size(); ++i)
|
||||
Log(Debug::Verbose) << "#" << i << " " << LuaUtil::scriptCfgToString(mConfiguration[i]);
|
||||
mMenuScripts.setAutoStartConf(mConfiguration.getMenuConf());
|
||||
mGlobalScripts.setAutoStartConf(mConfiguration.getGlobalConf());
|
||||
}
|
||||
|
||||
void LuaManager::init()
|
||||
{
|
||||
Context context;
|
||||
context.mIsMenu = false;
|
||||
context.mIsGlobal = true;
|
||||
context.mLuaManager = this;
|
||||
context.mLua = &mLua;
|
||||
@ -88,28 +96,42 @@ namespace MWLua
|
||||
localContext.mIsGlobal = false;
|
||||
localContext.mSerializer = mLocalSerializer.get();
|
||||
|
||||
Context menuContext = context;
|
||||
menuContext.mIsMenu = true;
|
||||
|
||||
for (const auto& [name, package] : initCommonPackages(context))
|
||||
mLua.addCommonPackage(name, package);
|
||||
for (const auto& [name, package] : initGlobalPackages(context))
|
||||
mGlobalScripts.addPackage(name, package);
|
||||
for (const auto& [name, package] : initMenuPackages(menuContext))
|
||||
mMenuScripts.addPackage(name, package);
|
||||
|
||||
mLocalPackages = initLocalPackages(localContext);
|
||||
|
||||
mPlayerPackages = initPlayerPackages(localContext);
|
||||
mPlayerPackages.insert(mLocalPackages.begin(), mLocalPackages.end());
|
||||
|
||||
LuaUtil::LuaStorage::initLuaBindings(mLua.sol());
|
||||
mGlobalScripts.addPackage(
|
||||
"openmw.storage", LuaUtil::LuaStorage::initGlobalPackage(mLua.sol(), &mGlobalStorage));
|
||||
mMenuScripts.addPackage(
|
||||
"openmw.storage", LuaUtil::LuaStorage::initMenuPackage(mLua.sol(), &mGlobalStorage, &mPlayerStorage));
|
||||
mLocalPackages["openmw.storage"] = LuaUtil::LuaStorage::initLocalPackage(mLua.sol(), &mGlobalStorage);
|
||||
mPlayerPackages["openmw.storage"]
|
||||
= LuaUtil::LuaStorage::initPlayerPackage(mLua.sol(), &mGlobalStorage, &mPlayerStorage);
|
||||
|
||||
mPlayerStorage.setActive(true);
|
||||
mGlobalStorage.setActive(false);
|
||||
|
||||
initConfiguration();
|
||||
mInitialized = true;
|
||||
mMenuScripts.addAutoStartedScripts();
|
||||
}
|
||||
|
||||
void LuaManager::loadPermanentStorage(const std::filesystem::path& userConfigPath)
|
||||
{
|
||||
mPlayerStorage.setActive(true);
|
||||
mGlobalStorage.setActive(true);
|
||||
const auto globalPath = userConfigPath / "global_storage.bin";
|
||||
const auto playerPath = userConfigPath / "player_storage.bin";
|
||||
if (std::filesystem::exists(globalPath))
|
||||
@ -120,7 +142,8 @@ namespace MWLua
|
||||
|
||||
void LuaManager::savePermanentStorage(const std::filesystem::path& userConfigPath)
|
||||
{
|
||||
mGlobalStorage.save(userConfigPath / "global_storage.bin");
|
||||
if (mGlobalScriptsStarted)
|
||||
mGlobalStorage.save(userConfigPath / "global_storage.bin");
|
||||
mPlayerStorage.save(userConfigPath / "player_storage.bin");
|
||||
}
|
||||
|
||||
@ -143,6 +166,10 @@ namespace MWLua
|
||||
|
||||
mObjectLists.update();
|
||||
|
||||
for (auto scripts : mQueuedAutoStartedScripts)
|
||||
scripts->addAutoStartedScripts();
|
||||
mQueuedAutoStartedScripts.clear();
|
||||
|
||||
std::erase_if(mActiveLocalScripts,
|
||||
[](const LocalScripts* l) { return l->getPtrOrEmpty().isEmpty() || l->getPtrOrEmpty().mRef->isDeleted(); });
|
||||
|
||||
@ -206,9 +233,6 @@ namespace MWLua
|
||||
|
||||
void LuaManager::synchronizedUpdate()
|
||||
{
|
||||
if (mPlayer.isEmpty())
|
||||
return; // The game is not started yet.
|
||||
|
||||
if (mNewGameStarted)
|
||||
{
|
||||
mNewGameStarted = false;
|
||||
@ -219,18 +243,25 @@ namespace MWLua
|
||||
|
||||
// We apply input events in `synchronizedUpdate` rather than in `update` in order to reduce input latency.
|
||||
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();
|
||||
|
||||
for (const auto& event : mMenuInputEvents)
|
||||
mMenuScripts.processInputEvent(event);
|
||||
mMenuInputEvents.clear();
|
||||
if (playerScripts && !windowManager->containsMode(MWGui::GM_MainMenu))
|
||||
{
|
||||
for (const auto& event : mInputEvents)
|
||||
playerScripts->processInputEvent(event);
|
||||
}
|
||||
mInputEvents.clear();
|
||||
mLuaEvents.callMenuEventHandlers();
|
||||
double frameDuration = MWBase::Environment::get().getWorld()->getTimeManager()->isPaused()
|
||||
? 0.0
|
||||
: MWBase::Environment::get().getFrameDuration();
|
||||
mInputActions.update(frameDuration);
|
||||
mMenuScripts.onFrame(frameDuration);
|
||||
if (playerScripts)
|
||||
playerScripts->onFrame(frameDuration);
|
||||
mProcessingInputEvents = false;
|
||||
@ -274,14 +305,14 @@ namespace MWLua
|
||||
|
||||
void LuaManager::clear()
|
||||
{
|
||||
LuaUi::clearUserInterface();
|
||||
LuaUi::clearGameInterface();
|
||||
mUiResourceManager.clear();
|
||||
MWBase::Environment::get().getWindowManager()->setConsoleMode("");
|
||||
MWBase::Environment::get().getWorld()->getPostProcessor()->disableDynamicShaders();
|
||||
mActiveLocalScripts.clear();
|
||||
mLuaEvents.clear();
|
||||
mEngineEvents.clear();
|
||||
mInputEvents.clear();
|
||||
mMenuInputEvents.clear();
|
||||
mObjectLists.clear();
|
||||
mGlobalScripts.removeAllScripts();
|
||||
mGlobalScriptsStarted = false;
|
||||
@ -293,7 +324,9 @@ namespace MWLua
|
||||
mPlayer.getRefData().setLuaScripts(nullptr);
|
||||
mPlayer = MWWorld::Ptr();
|
||||
}
|
||||
mGlobalStorage.setActive(true);
|
||||
mGlobalStorage.clearTemporaryAndRemoveCallbacks();
|
||||
mGlobalStorage.setActive(false);
|
||||
mPlayerStorage.clearTemporaryAndRemoveCallbacks();
|
||||
mInputActions.clear();
|
||||
mInputTriggers.clear();
|
||||
@ -314,7 +347,7 @@ namespace MWLua
|
||||
if (!localScripts)
|
||||
{
|
||||
localScripts = createLocalScripts(ptr);
|
||||
localScripts->addAutoStartedScripts();
|
||||
mQueuedAutoStartedScripts.push_back(localScripts);
|
||||
}
|
||||
mActiveLocalScripts.insert(localScripts);
|
||||
mEngineEvents.addToQueue(EngineEvents::OnActive{ getId(ptr) });
|
||||
@ -322,17 +355,28 @@ namespace MWLua
|
||||
|
||||
void LuaManager::newGameStarted()
|
||||
{
|
||||
mGlobalStorage.setActive(true);
|
||||
mInputEvents.clear();
|
||||
mGlobalScripts.addAutoStartedScripts();
|
||||
mGlobalScriptsStarted = true;
|
||||
mNewGameStarted = true;
|
||||
mMenuScripts.stateChanged();
|
||||
}
|
||||
|
||||
void LuaManager::gameLoaded()
|
||||
{
|
||||
mGlobalStorage.setActive(true);
|
||||
if (!mGlobalScriptsStarted)
|
||||
mGlobalScripts.addAutoStartedScripts();
|
||||
mGlobalScriptsStarted = true;
|
||||
mMenuScripts.stateChanged();
|
||||
}
|
||||
|
||||
void LuaManager::gameEnded()
|
||||
{
|
||||
// TODO: disable scripts and global storage when the game is actually unloaded
|
||||
// mGlobalStorage.setActive(false);
|
||||
mMenuScripts.stateChanged();
|
||||
}
|
||||
|
||||
void LuaManager::uiModeChanged(const MWWorld::Ptr& arg)
|
||||
@ -419,7 +463,7 @@ namespace MWLua
|
||||
if (!autoStartConf.empty())
|
||||
{
|
||||
localScripts = createLocalScripts(ptr, std::move(autoStartConf));
|
||||
localScripts->addAutoStartedScripts(); // TODO: put to a queue and apply on next `update()`
|
||||
mQueuedAutoStartedScripts.push_back(localScripts);
|
||||
}
|
||||
}
|
||||
if (localScripts)
|
||||
@ -445,6 +489,7 @@ namespace MWLua
|
||||
{
|
||||
mInputEvents.push_back(event);
|
||||
}
|
||||
mMenuInputEvents.push_back(event);
|
||||
}
|
||||
|
||||
MWBase::LuaManager::ActorControls* LuaManager::getActorControls(const MWWorld::Ptr& ptr) const
|
||||
@ -526,6 +571,10 @@ namespace MWLua
|
||||
throw std::runtime_error("Last generated RefNum is invalid");
|
||||
MWBase::Environment::get().getWorldModel()->setLastGeneratedRefNum(lastGenerated);
|
||||
|
||||
// TODO: don't execute scripts right away, it will be necessary in multiplayer where global storage requires
|
||||
// initialization. For now just set global storage as active slightly before it would be set by gameLoaded()
|
||||
mGlobalStorage.setActive(true);
|
||||
|
||||
ESM::LuaScripts globalScripts;
|
||||
globalScripts.load(reader);
|
||||
mLuaEvents.load(mLua.sol(), reader, mContentFileMapping, mGlobalLoader.get());
|
||||
@ -564,7 +613,9 @@ namespace MWLua
|
||||
{
|
||||
Log(Debug::Info) << "Reload Lua";
|
||||
|
||||
LuaUi::clearUserInterface();
|
||||
LuaUi::clearGameInterface();
|
||||
LuaUi::clearMenuInterface();
|
||||
LuaUi::clearSettings();
|
||||
MWBase::Environment::get().getWindowManager()->setConsoleMode("");
|
||||
MWBase::Environment::get().getL10nManager()->dropCache();
|
||||
mUiResourceManager.clear();
|
||||
@ -573,26 +624,49 @@ namespace MWLua
|
||||
mInputTriggers.clear();
|
||||
initConfiguration();
|
||||
|
||||
{ // Reload global scripts
|
||||
ESM::LuaScripts globalData;
|
||||
|
||||
if (mGlobalScriptsStarted)
|
||||
{
|
||||
mGlobalScripts.setSavedDataDeserializer(mGlobalSerializer.get());
|
||||
ESM::LuaScripts data;
|
||||
mGlobalScripts.save(data);
|
||||
mGlobalScripts.save(globalData);
|
||||
mGlobalStorage.clearTemporaryAndRemoveCallbacks();
|
||||
mGlobalScripts.load(data);
|
||||
}
|
||||
|
||||
std::unordered_map<ESM::RefNum, ESM::LuaScripts> localData;
|
||||
|
||||
for (const auto& [id, ptr] : MWBase::Environment::get().getWorldModel()->getPtrRegistryView())
|
||||
{ // Reload local scripts
|
||||
{
|
||||
LocalScripts* scripts = ptr.getRefData().getLuaScripts();
|
||||
if (scripts == nullptr)
|
||||
continue;
|
||||
scripts->setSavedDataDeserializer(mLocalSerializer.get());
|
||||
ESM::LuaScripts data;
|
||||
scripts->save(data);
|
||||
scripts->load(data);
|
||||
localData[id] = data;
|
||||
}
|
||||
|
||||
mMenuScripts.removeAllScripts();
|
||||
|
||||
mPlayerStorage.clearTemporaryAndRemoveCallbacks();
|
||||
|
||||
mMenuScripts.addAutoStartedScripts();
|
||||
|
||||
for (const auto& [id, ptr] : MWBase::Environment::get().getWorldModel()->getPtrRegistryView())
|
||||
{
|
||||
LocalScripts* scripts = ptr.getRefData().getLuaScripts();
|
||||
if (scripts == nullptr)
|
||||
continue;
|
||||
scripts->load(localData[id]);
|
||||
}
|
||||
|
||||
for (LocalScripts* scripts : mActiveLocalScripts)
|
||||
scripts->setActive(true);
|
||||
|
||||
if (mGlobalScriptsStarted)
|
||||
{
|
||||
mGlobalScripts.load(globalData);
|
||||
}
|
||||
}
|
||||
|
||||
void LuaManager::handleConsoleCommand(
|
||||
@ -601,16 +675,16 @@ namespace MWLua
|
||||
PlayerScripts* playerScripts = nullptr;
|
||||
if (!mPlayer.isEmpty())
|
||||
playerScripts = dynamic_cast<PlayerScripts*>(mPlayer.getRefData().getLuaScripts());
|
||||
if (!playerScripts)
|
||||
bool processed = mMenuScripts.consoleCommand(consoleMode, command);
|
||||
if (playerScripts)
|
||||
{
|
||||
MWBase::Environment::get().getWindowManager()->printToConsole(
|
||||
"You must enter a game session to run Lua commands\n", MWBase::WindowManager::sConsoleColor_Error);
|
||||
return;
|
||||
sol::object selected = sol::nil;
|
||||
if (!selectedPtr.isEmpty())
|
||||
selected = sol::make_object(mLua.sol(), LObject(getId(selectedPtr)));
|
||||
if (playerScripts->consoleCommand(consoleMode, command, selected))
|
||||
processed = true;
|
||||
}
|
||||
sol::object selected = sol::nil;
|
||||
if (!selectedPtr.isEmpty())
|
||||
selected = sol::make_object(mLua.sol(), LObject(getId(selectedPtr)));
|
||||
if (!playerScripts->consoleCommand(consoleMode, command, selected))
|
||||
if (!processed)
|
||||
MWBase::Environment::get().getWindowManager()->printToConsole(
|
||||
"No Lua handlers for console\n", MWBase::WindowManager::sConsoleColor_Error);
|
||||
}
|
||||
@ -744,6 +818,7 @@ namespace MWLua
|
||||
for (size_t i = 0; i < mConfiguration.size(); ++i)
|
||||
{
|
||||
bool isGlobal = mConfiguration[i].mFlags & ESM::LuaScriptCfg::sGlobal;
|
||||
bool isMenu = mConfiguration[i].mFlags & ESM::LuaScriptCfg::sMenu;
|
||||
|
||||
out << std::left;
|
||||
out << " " << std::setw(nameW) << mConfiguration[i].mScriptPath;
|
||||
@ -756,6 +831,8 @@ namespace MWLua
|
||||
|
||||
if (isGlobal)
|
||||
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())
|
||||
out << std::setw(valueW * 2) << "NA (not selected) ";
|
||||
else if (!selectedScripts || !selectedScripts->hasScript(i))
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "globalscripts.hpp"
|
||||
#include "localscripts.hpp"
|
||||
#include "luaevents.hpp"
|
||||
#include "menuscripts.hpp"
|
||||
#include "object.hpp"
|
||||
#include "objectlists.hpp"
|
||||
|
||||
@ -34,6 +35,7 @@ namespace MWLua
|
||||
LuaManager(const VFS::Manager* vfs, const std::filesystem::path& libsDir);
|
||||
LuaManager(const LuaManager&) = delete;
|
||||
LuaManager(LuaManager&&) = delete;
|
||||
~LuaManager();
|
||||
|
||||
// Called by engine.cpp when the environment is fully initialized.
|
||||
void init();
|
||||
@ -67,6 +69,7 @@ namespace MWLua
|
||||
// LuaManager queues these events and propagates to scripts on the next `update` call.
|
||||
void newGameStarted() override;
|
||||
void gameLoaded() override;
|
||||
void gameEnded() override;
|
||||
void objectAddedToScene(const MWWorld::Ptr& ptr) override;
|
||||
void objectRemovedFromScene(const MWWorld::Ptr& ptr) override;
|
||||
void inputEvent(const InputEvent& event) override;
|
||||
@ -171,15 +174,18 @@ namespace MWLua
|
||||
std::map<std::string, sol::object> mLocalPackages;
|
||||
std::map<std::string, sol::object> mPlayerPackages;
|
||||
|
||||
MenuScripts mMenuScripts{ &mLua };
|
||||
GlobalScripts mGlobalScripts{ &mLua };
|
||||
std::set<LocalScripts*> mActiveLocalScripts;
|
||||
std::vector<LocalScripts*> mQueuedAutoStartedScripts;
|
||||
ObjectLists mObjectLists;
|
||||
|
||||
MWWorld::Ptr mPlayer;
|
||||
|
||||
LuaEvents mLuaEvents{ mGlobalScripts };
|
||||
LuaEvents mLuaEvents{ mGlobalScripts, mMenuScripts };
|
||||
EngineEvents mEngineEvents{ mGlobalScripts };
|
||||
std::vector<MWBase::LuaManager::InputEvent> mInputEvents;
|
||||
std::vector<MWBase::LuaManager::InputEvent> mMenuInputEvents;
|
||||
|
||||
std::unique_ptr<LuaUtil::UserdataSerializer> mGlobalSerializer;
|
||||
std::unique_ptr<LuaUtil::UserdataSerializer> mLocalSerializer;
|
||||
|
124
apps/openmw/mwlua/menuscripts.cpp
Normal file
124
apps/openmw/mwlua/menuscripts.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
#include "menuscripts.hpp"
|
||||
|
||||
#include <components/misc/strings/lower.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;
|
||||
slotInfo["timePlayed"] = slot.mProfile.mTimePlayed;
|
||||
sol::table contentFiles(lua, sol::create);
|
||||
for (size_t i = 0; i < slot.mProfile.mContentFiles.size(); ++i)
|
||||
contentFiles[i + 1] = Misc::StringUtils::lowerCase(slot.mProfile.mContentFiles[i]);
|
||||
|
||||
{
|
||||
auto system_time = std::chrono::system_clock::now()
|
||||
- (std::filesystem::file_time_type::clock::now() - slot.mTimeStamp);
|
||||
slotInfo["creationTime"] = std::chrono::duration<double>(system_time.time_since_epoch()).count();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
57
apps/openmw/mwlua/menuscripts.hpp
Normal file
57
apps/openmw/mwlua/menuscripts.hpp
Normal file
@ -0,0 +1,57 @@
|
||||
#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"
|
||||
#include "inputprocessor.hpp"
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
|
||||
sol::table initMenuPackage(const Context& context);
|
||||
|
||||
class MenuScripts : public LuaUtil::ScriptsContainer
|
||||
{
|
||||
public:
|
||||
MenuScripts(LuaUtil::LuaState* lua)
|
||||
: LuaUtil::ScriptsContainer(lua, "Menu")
|
||||
, mInputProcessor(this)
|
||||
{
|
||||
registerEngineHandlers({ &mOnFrameHandlers, &mStateChanged, &mConsoleCommandHandlers, &mUiModeChanged });
|
||||
}
|
||||
|
||||
void processInputEvent(const MWBase::LuaManager::InputEvent& event)
|
||||
{
|
||||
mInputProcessor.processInputEvent(event);
|
||||
}
|
||||
|
||||
void onFrame(float dt) { callEngineHandlers(mOnFrameHandlers, dt); }
|
||||
|
||||
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:
|
||||
friend class MWLua::InputProcessor<MenuScripts>;
|
||||
MWLua::InputProcessor<MenuScripts> mInputProcessor;
|
||||
EngineHandlerList mOnFrameHandlers{ "onFrame" };
|
||||
EngineHandlerList mStateChanged{ "onStateChanged" };
|
||||
EngineHandlerList mConsoleCommandHandlers{ "onConsoleCommand" };
|
||||
EngineHandlerList mUiModeChanged{ "_onUiModeChanged" };
|
||||
};
|
||||
}
|
||||
|
||||
#endif // MWLUA_GLOBALSCRIPTS_H
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "../mwbase/luamanager.hpp"
|
||||
|
||||
#include "inputprocessor.hpp"
|
||||
#include "localscripts.hpp"
|
||||
|
||||
namespace MWLua
|
||||
@ -17,42 +18,14 @@ namespace MWLua
|
||||
public:
|
||||
PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj)
|
||||
: LocalScripts(lua, obj)
|
||||
, mInputProcessor(this)
|
||||
{
|
||||
registerEngineHandlers({ &mConsoleCommandHandlers, &mKeyPressHandlers, &mKeyReleaseHandlers,
|
||||
&mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers, &mActionHandlers, &mOnFrameHandlers,
|
||||
&mTouchpadPressed, &mTouchpadReleased, &mTouchpadMoved, &mQuestUpdate, &mUiModeChanged });
|
||||
registerEngineHandlers({ &mConsoleCommandHandlers, &mOnFrameHandlers, &mQuestUpdate, &mUiModeChanged });
|
||||
}
|
||||
|
||||
void processInputEvent(const MWBase::LuaManager::InputEvent& event)
|
||||
{
|
||||
using InputEvent = MWBase::LuaManager::InputEvent;
|
||||
switch (event.mType)
|
||||
{
|
||||
case InputEvent::KeyPressed:
|
||||
callEngineHandlers(mKeyPressHandlers, std::get<SDL_Keysym>(event.mValue));
|
||||
break;
|
||||
case InputEvent::KeyReleased:
|
||||
callEngineHandlers(mKeyReleaseHandlers, std::get<SDL_Keysym>(event.mValue));
|
||||
break;
|
||||
case InputEvent::ControllerPressed:
|
||||
callEngineHandlers(mControllerButtonPressHandlers, std::get<int>(event.mValue));
|
||||
break;
|
||||
case InputEvent::ControllerReleased:
|
||||
callEngineHandlers(mControllerButtonReleaseHandlers, std::get<int>(event.mValue));
|
||||
break;
|
||||
case InputEvent::Action:
|
||||
callEngineHandlers(mActionHandlers, std::get<int>(event.mValue));
|
||||
break;
|
||||
case InputEvent::TouchPressed:
|
||||
callEngineHandlers(mTouchpadPressed, std::get<SDLUtil::TouchEvent>(event.mValue));
|
||||
break;
|
||||
case InputEvent::TouchReleased:
|
||||
callEngineHandlers(mTouchpadReleased, std::get<SDLUtil::TouchEvent>(event.mValue));
|
||||
break;
|
||||
case InputEvent::TouchMoved:
|
||||
callEngineHandlers(mTouchpadMoved, std::get<SDLUtil::TouchEvent>(event.mValue));
|
||||
break;
|
||||
}
|
||||
mInputProcessor.processInputEvent(event);
|
||||
}
|
||||
|
||||
void onFrame(float dt) { callEngineHandlers(mOnFrameHandlers, dt); }
|
||||
@ -75,16 +48,10 @@ namespace MWLua
|
||||
}
|
||||
|
||||
private:
|
||||
friend class MWLua::InputProcessor<PlayerScripts>;
|
||||
InputProcessor<PlayerScripts> mInputProcessor;
|
||||
EngineHandlerList mConsoleCommandHandlers{ "onConsoleCommand" };
|
||||
EngineHandlerList mKeyPressHandlers{ "onKeyPress" };
|
||||
EngineHandlerList mKeyReleaseHandlers{ "onKeyRelease" };
|
||||
EngineHandlerList mControllerButtonPressHandlers{ "onControllerButtonPress" };
|
||||
EngineHandlerList mControllerButtonReleaseHandlers{ "onControllerButtonRelease" };
|
||||
EngineHandlerList mActionHandlers{ "onInputAction" };
|
||||
EngineHandlerList mOnFrameHandlers{ "onFrame" };
|
||||
EngineHandlerList mTouchpadPressed{ "onTouchPress" };
|
||||
EngineHandlerList mTouchpadReleased{ "onTouchRelease" };
|
||||
EngineHandlerList mTouchpadMoved{ "onTouchMove" };
|
||||
EngineHandlerList mQuestUpdate{ "onQuestUpdate" };
|
||||
EngineHandlerList mUiModeChanged{ "_onUiModeChanged" };
|
||||
};
|
||||
|
@ -77,7 +77,11 @@ namespace MWLua
|
||||
{
|
||||
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) {
|
||||
auto args = getPlaySoundArgs(options);
|
||||
@ -121,7 +125,8 @@ namespace MWLua
|
||||
|
||||
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)
|
||||
|
@ -36,14 +36,18 @@ namespace sol
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
static void verifyPlayer(const Object& player)
|
||||
{
|
||||
if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr())
|
||||
throw std::runtime_error("The argument must be a player!");
|
||||
}
|
||||
|
||||
void addPlayerQuestBindings(sol::table& player, const Context& context)
|
||||
void addPlayerBindings(sol::table player, const Context& context)
|
||||
{
|
||||
MWBase::Journal* const journal = MWBase::Environment::get().getJournal();
|
||||
|
||||
player["quests"] = [](const Object& player) {
|
||||
if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr())
|
||||
throw std::runtime_error("The argument must be a player!");
|
||||
verifyPlayer(player);
|
||||
bool allowChanges = dynamic_cast<const GObject*>(&player) != nullptr
|
||||
|| dynamic_cast<const SelfObject*>(&player) != nullptr;
|
||||
return Quests{ .mMutable = allowChanges };
|
||||
@ -135,33 +139,30 @@ namespace MWLua
|
||||
|
||||
MWBase::InputManager* input = MWBase::Environment::get().getInputManager();
|
||||
player["getControlSwitch"] = [input](const Object& player, std::string_view key) {
|
||||
if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr())
|
||||
throw std::runtime_error("The argument must be a player.");
|
||||
verifyPlayer(player);
|
||||
return input->getControlSwitch(key);
|
||||
};
|
||||
player["isTeleportingEnabled"] = [](const Object& player) -> bool {
|
||||
if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr())
|
||||
throw std::runtime_error("The argument must be a player.");
|
||||
return MWBase::Environment::get().getWorld()->isTeleportingEnabled();
|
||||
};
|
||||
player["setTeleportingEnabled"] = [](const Object& player, bool state) {
|
||||
if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr())
|
||||
throw std::runtime_error("The argument must be a player.");
|
||||
if (dynamic_cast<const LObject*>(&player) && !dynamic_cast<const SelfObject*>(&player))
|
||||
throw std::runtime_error("Only player and global scripts can toggle teleportation.");
|
||||
MWBase::Environment::get().getWorld()->enableTeleporting(state);
|
||||
};
|
||||
player["setControlSwitch"] = [input](const Object& player, std::string_view key, bool v) {
|
||||
if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr())
|
||||
throw std::runtime_error("The argument must be a player.");
|
||||
verifyPlayer(player);
|
||||
if (dynamic_cast<const LObject*>(&player) && !dynamic_cast<const SelfObject*>(&player))
|
||||
throw std::runtime_error("Only player and global scripts can toggle control switches.");
|
||||
input->toggleControlSwitch(key, v);
|
||||
};
|
||||
}
|
||||
player["isTeleportingEnabled"] = [](const Object& player) -> bool {
|
||||
verifyPlayer(player);
|
||||
return MWBase::Environment::get().getWorld()->isTeleportingEnabled();
|
||||
};
|
||||
player["setTeleportingEnabled"] = [](const Object& player, bool state) {
|
||||
verifyPlayer(player);
|
||||
if (dynamic_cast<const LObject*>(&player) && !dynamic_cast<const SelfObject*>(&player))
|
||||
throw std::runtime_error("Only player and global scripts can toggle teleportation.");
|
||||
MWBase::Environment::get().getWorld()->enableTeleporting(state);
|
||||
};
|
||||
player["sendMenuEvent"] = [context](const Object& player, std::string eventName, const sol::object& eventData) {
|
||||
verifyPlayer(player);
|
||||
context.mLuaEvents->addMenuEvent({ std::move(eventName), LuaUtil::serialize(eventData) });
|
||||
};
|
||||
|
||||
void addPlayerBindings(sol::table player, const Context& context)
|
||||
{
|
||||
player["getCrimeLevel"] = [](const Object& o) -> int {
|
||||
const MWWorld::Class& cls = o.ptr().getClass();
|
||||
return cls.getNpcStats(o.ptr()).getBounty();
|
||||
@ -169,6 +170,5 @@ namespace MWLua
|
||||
player["isCharGenFinished"] = [](const Object&) -> bool {
|
||||
return MWBase::Environment::get().getWorld()->getGlobalFloat(MWWorld::Globals::sCharGenState) == -1;
|
||||
};
|
||||
addPlayerQuestBindings(player, context);
|
||||
}
|
||||
}
|
||||
|
@ -164,6 +164,10 @@ namespace MWLua
|
||||
sol::table initTypesPackage(const Context& context)
|
||||
{
|
||||
auto* lua = context.mLua;
|
||||
|
||||
if (lua->sol()["openmw_types"] != sol::nil)
|
||||
return lua->sol()["openmw_types"];
|
||||
|
||||
sol::table types(lua->sol(), sol::create);
|
||||
auto addType = [&](std::string_view name, std::vector<ESM::RecNameInts> recTypes,
|
||||
std::optional<std::string_view> base = std::nullopt) -> sol::table {
|
||||
@ -255,6 +259,7 @@ namespace MWLua
|
||||
packageToType[t] = type;
|
||||
}
|
||||
|
||||
return LuaUtil::makeReadOnly(types);
|
||||
lua->sol()["openmw_types"] = LuaUtil::makeReadOnly(types);
|
||||
return lua->sol()["openmw_types"];
|
||||
}
|
||||
}
|
||||
|
@ -89,33 +89,10 @@ namespace MWLua
|
||||
}();
|
||||
}
|
||||
|
||||
sol::table initUserInterfacePackage(const Context& context)
|
||||
sol::table registerUiApi(const Context& context, bool menu)
|
||||
{
|
||||
MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager();
|
||||
|
||||
auto element = context.mLua->sol().new_usertype<LuaUi::Element>("Element");
|
||||
element[sol::meta_function::to_string] = [](const LuaUi::Element& element) {
|
||||
std::stringstream res;
|
||||
res << "UiElement";
|
||||
if (element.mLayer != "")
|
||||
res << "[" << element.mLayer << "]";
|
||||
return res.str();
|
||||
};
|
||||
element["layout"] = sol::property([](LuaUi::Element& element) { return element.mLayout; },
|
||||
[](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; });
|
||||
element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr<LuaUi::Element>& element) {
|
||||
if (element->mDestroy || element->mUpdate)
|
||||
return;
|
||||
element->mUpdate = true;
|
||||
luaManager->addAction([element] { wrapAction(element, [&] { element->update(); }); }, "Update UI");
|
||||
};
|
||||
element["destroy"] = [luaManager = context.mLuaManager](const std::shared_ptr<LuaUi::Element>& element) {
|
||||
if (element->mDestroy)
|
||||
return;
|
||||
element->mDestroy = true;
|
||||
luaManager->addAction([element] { wrapAction(element, [&] { element->destroy(); }); }, "Destroy UI");
|
||||
};
|
||||
|
||||
sol::table api = context.mLua->newTable();
|
||||
api["_setHudVisibility"] = [luaManager = context.mLuaManager](bool state) {
|
||||
luaManager->addAction([state] { MWBase::Environment::get().getWindowManager()->setHudVisibility(state); });
|
||||
@ -136,6 +113,7 @@ namespace MWLua
|
||||
api["setConsoleMode"] = [luaManager = context.mLuaManager, windowManager](std::string_view 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) {
|
||||
if (obj == sol::nil)
|
||||
luaManager->addAction([windowManager] { windowManager->setConsoleSelectedObject(MWWorld::Ptr()); });
|
||||
@ -148,24 +126,20 @@ namespace MWLua
|
||||
}
|
||||
};
|
||||
api["content"] = LuaUi::loadContentConstructor(context.mLua);
|
||||
api["create"] = [luaManager = context.mLuaManager](const sol::table& layout) {
|
||||
auto element = LuaUi::Element::make(layout);
|
||||
|
||||
api["create"] = [luaManager = context.mLuaManager, menu](const sol::table& layout) {
|
||||
auto element = LuaUi::Element::make(layout, menu);
|
||||
luaManager->addAction([element] { wrapAction(element, [&] { element->create(); }); }, "Create UI");
|
||||
return element;
|
||||
};
|
||||
api["updateAll"] = [context]() {
|
||||
LuaUi::Element::forEach([](LuaUi::Element* e) { e->mUpdate = true; });
|
||||
context.mLuaManager->addAction(
|
||||
[]() { LuaUi::Element::forEach([](LuaUi::Element* e) { e->update(); }); }, "Update all UI elements");
|
||||
|
||||
api["updateAll"] = [luaManager = context.mLuaManager, menu]() {
|
||||
LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { e->mUpdate = true; });
|
||||
luaManager->addAction([menu]() { LuaUi::Element::forEach(menu, [](LuaUi::Element* e) { e->update(); }); },
|
||||
"Update all menu UI elements");
|
||||
};
|
||||
api["_getMenuTransparency"] = []() -> float { return Settings::gui().mMenuTransparency; };
|
||||
|
||||
auto uiLayer = context.mLua->sol().new_usertype<LuaUi::Layer>("UiLayer");
|
||||
uiLayer["name"] = sol::readonly_property([](LuaUi::Layer& self) -> std::string_view { return self.name(); });
|
||||
uiLayer["size"] = sol::readonly_property([](LuaUi::Layer& self) { return self.size(); });
|
||||
uiLayer[sol::meta_function::to_string]
|
||||
= [](LuaUi::Layer& self) { return Misc::StringUtils::format("UiLayer(%s)", self.name()); };
|
||||
|
||||
sol::table layersTable = context.mLua->newTable();
|
||||
layersTable["indexOf"] = [](std::string_view name) -> sol::optional<size_t> {
|
||||
size_t index = LuaUi::Layer::indexOf(name);
|
||||
@ -228,6 +202,7 @@ namespace MWLua
|
||||
{ "Center", LuaUi::Alignment::Center }, { "End", LuaUi::Alignment::End } }));
|
||||
|
||||
api["registerSettingsPage"] = &LuaUi::registerSettingsPage;
|
||||
api["removeSettingsPage"] = &LuaUi::removeSettingsPage;
|
||||
|
||||
api["texture"] = [luaManager = context.mLuaManager](const sol::table& options) {
|
||||
LuaUi::TextureData data;
|
||||
@ -305,6 +280,56 @@ namespace MWLua
|
||||
// TODO
|
||||
// api["_showMouseCursor"] = [](bool) {};
|
||||
|
||||
return LuaUtil::makeReadOnly(api);
|
||||
return api;
|
||||
}
|
||||
|
||||
sol::table initUserInterfacePackage(const Context& context)
|
||||
{
|
||||
std::string_view menuCache = "openmw_ui_menu";
|
||||
std::string_view gameCache = "openmw_ui_game";
|
||||
std::string_view cacheKey = context.mIsMenu ? menuCache : gameCache;
|
||||
{
|
||||
sol::state_view& lua = context.mLua->sol();
|
||||
if (lua[cacheKey] != sol::nil)
|
||||
return lua[cacheKey];
|
||||
}
|
||||
|
||||
auto element = context.mLua->sol().new_usertype<LuaUi::Element>("UiElement");
|
||||
element[sol::meta_function::to_string] = [](const LuaUi::Element& element) {
|
||||
std::stringstream res;
|
||||
res << "UiElement";
|
||||
if (element.mLayer != "")
|
||||
res << "[" << element.mLayer << "]";
|
||||
return res.str();
|
||||
};
|
||||
element["layout"] = sol::property([](const LuaUi::Element& element) { return element.mLayout; },
|
||||
[](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; });
|
||||
element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr<LuaUi::Element>& element) {
|
||||
if (element->mDestroy || element->mUpdate)
|
||||
return;
|
||||
element->mUpdate = true;
|
||||
luaManager->addAction([element] { wrapAction(element, [&] { element->update(); }); }, "Update UI");
|
||||
};
|
||||
element["destroy"] = [luaManager = context.mLuaManager](const std::shared_ptr<LuaUi::Element>& element) {
|
||||
if (element->mDestroy)
|
||||
return;
|
||||
element->mDestroy = true;
|
||||
luaManager->addAction(
|
||||
[element] { wrapAction(element, [&] { LuaUi::Element::erase(element.get()); }); }, "Destroy UI");
|
||||
};
|
||||
|
||||
auto uiLayer = context.mLua->sol().new_usertype<LuaUi::Layer>("UiLayer");
|
||||
uiLayer["name"] = sol::readonly_property([](LuaUi::Layer& self) { return self.name(); });
|
||||
uiLayer["size"] = sol::readonly_property([](LuaUi::Layer& self) { return self.size(); });
|
||||
uiLayer[sol::meta_function::to_string]
|
||||
= [](LuaUi::Layer& self) { return Misc::StringUtils::format("UiLayer(%s)", self.name()); };
|
||||
|
||||
sol::table menuApi = registerUiApi(context, true);
|
||||
sol::table gameApi = registerUiApi(context, false);
|
||||
|
||||
sol::state_view& lua = context.mLua->sol();
|
||||
lua[menuCache] = LuaUtil::makeReadOnly(menuApi);
|
||||
lua[gameCache] = LuaUtil::makeReadOnly(gameApi);
|
||||
return lua[cacheKey];
|
||||
}
|
||||
}
|
||||
|
215
apps/openmw/mwlua/worldbindings.cpp
Normal file
215
apps/openmw/mwlua/worldbindings.cpp
Normal file
@ -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);
|
||||
}
|
||||
}
|
13
apps/openmw/mwlua/worldbindings.hpp
Normal file
13
apps/openmw/mwlua/worldbindings.hpp
Normal file
@ -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
|
@ -68,6 +68,8 @@ void MWState::StateManager::cleanup(bool force)
|
||||
mLastSavegame.clear();
|
||||
|
||||
MWMechanics::CreatureStats::cleanup();
|
||||
|
||||
endGame();
|
||||
}
|
||||
MWBase::Environment::get().getLuaManager()->clear();
|
||||
}
|
||||
@ -170,10 +172,10 @@ void MWState::StateManager::newGame(bool bypass)
|
||||
{
|
||||
Log(Debug::Info) << "Starting a new game";
|
||||
MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup();
|
||||
MWBase::Environment::get().getLuaManager()->newGameStarted();
|
||||
MWBase::Environment::get().getWorld()->startNewGame(bypass);
|
||||
|
||||
mState = State_Running;
|
||||
MWBase::Environment::get().getLuaManager()->newGameStarted();
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0);
|
||||
MWBase::Environment::get().getWindowManager()->fadeScreenIn(1);
|
||||
@ -197,11 +199,13 @@ void MWState::StateManager::newGame(bool bypass)
|
||||
void MWState::StateManager::endGame()
|
||||
{
|
||||
mState = State_Ended;
|
||||
MWBase::Environment::get().getLuaManager()->gameEnded();
|
||||
}
|
||||
|
||||
void MWState::StateManager::resumeGame()
|
||||
{
|
||||
mState = State_Running;
|
||||
MWBase::Environment::get().getLuaManager()->gameLoaded();
|
||||
}
|
||||
|
||||
void MWState::StateManager::saveGame(std::string_view description, const Slot* slot)
|
||||
@ -744,6 +748,18 @@ void MWState::StateManager::update(float duration)
|
||||
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu);
|
||||
}
|
||||
}
|
||||
|
||||
if (mNewGameRequest)
|
||||
{
|
||||
newGame();
|
||||
mNewGameRequest = false;
|
||||
}
|
||||
|
||||
if (mLoadRequest)
|
||||
{
|
||||
loadGame(*mLoadRequest);
|
||||
mLoadRequest = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
bool MWState::StateManager::confirmLoading(const std::vector<std::string_view>& missingFiles) const
|
||||
|
@ -14,6 +14,8 @@ namespace MWState
|
||||
{
|
||||
bool mQuitRequest;
|
||||
bool mAskLoadRecent;
|
||||
bool mNewGameRequest = false;
|
||||
std::optional<std::filesystem::path> mLoadRequest;
|
||||
State mState;
|
||||
CharacterManager mCharacterManager;
|
||||
double mTimePlayed;
|
||||
@ -39,6 +41,9 @@ namespace MWState
|
||||
|
||||
void askLoadRecent() override;
|
||||
|
||||
void requestNewGame() override { mNewGameRequest = true; }
|
||||
void requestLoad(const std::filesystem::path& filepath) override { mLoadRequest = filepath; }
|
||||
|
||||
State getState() const override;
|
||||
|
||||
void newGame(bool bypass = false) override;
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
#include "../mwbase/statemanager.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
@ -263,8 +264,9 @@ namespace MWWorld
|
||||
|
||||
void DateTimeManager::updateIsPaused()
|
||||
{
|
||||
auto stateManager = MWBase::Environment::get().getStateManager();
|
||||
auto wm = MWBase::Environment::get().getWindowManager();
|
||||
mPaused = !mPausedTags.empty() || wm->isConsoleMode() || wm->isPostProcessorHudVisible()
|
||||
|| wm->isInteractiveMessageBoxActive();
|
||||
|| wm->isInteractiveMessageBoxActive() || stateManager->getState() == MWBase::StateManager::State_NoGame;
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ namespace
|
||||
sol::state_view& mLua = luaState.sol();
|
||||
LuaUtil::LuaStorage::initLuaBindings(mLua);
|
||||
LuaUtil::LuaStorage storage(mLua);
|
||||
storage.setActive(true);
|
||||
|
||||
std::vector<std::string> callbackCalls;
|
||||
sol::table callbackHiddenData(mLua, sol::create);
|
||||
@ -65,6 +66,7 @@ namespace
|
||||
sol::state mLua;
|
||||
LuaUtil::LuaStorage::initLuaBindings(mLua);
|
||||
LuaUtil::LuaStorage storage(mLua);
|
||||
storage.setActive(true);
|
||||
mLua["mutable"] = storage.getMutableSection("test");
|
||||
mLua["ro"] = storage.getReadOnlySection("test");
|
||||
|
||||
@ -82,6 +84,7 @@ namespace
|
||||
sol::state mLua;
|
||||
LuaUtil::LuaStorage::initLuaBindings(mLua);
|
||||
LuaUtil::LuaStorage storage(mLua);
|
||||
storage.setActive(true);
|
||||
|
||||
mLua["permanent"] = storage.getMutableSection("permanent");
|
||||
mLua["temporary"] = storage.getMutableSection("temporary");
|
||||
@ -104,6 +107,7 @@ namespace
|
||||
mLua.safe_script("permanent:set('z', 4)");
|
||||
|
||||
LuaUtil::LuaStorage storage2(mLua);
|
||||
storage2.setActive(true);
|
||||
storage2.load(tmpFile);
|
||||
mLua["permanent"] = storage2.getMutableSection("permanent");
|
||||
mLua["temporary"] = storage2.getMutableSection("temporary");
|
||||
|
@ -20,8 +20,10 @@ namespace ESM
|
||||
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 sMerge = 1ull
|
||||
<< 3; // merge with configuration for this script from previous content files.
|
||||
// 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 mInitializationData; // Serialized Lua table. It is a binary data. Can contain '\0'.
|
||||
|
@ -18,6 +18,7 @@ namespace LuaUtil
|
||||
{ "GLOBAL", ESM::LuaScriptCfg::sGlobal },
|
||||
{ "CUSTOM", ESM::LuaScriptCfg::sCustom },
|
||||
{ "PLAYER", ESM::LuaScriptCfg::sPlayer },
|
||||
{ "MENU", ESM::LuaScriptCfg::sMenu },
|
||||
};
|
||||
|
||||
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;
|
||||
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 getPlayerConf() const { return getConfByFlag(ESM::LuaScriptCfg::sPlayer); }
|
||||
ScriptIdsWithInitializationData getLocalConf(
|
||||
|
@ -31,6 +31,7 @@ namespace LuaUtil
|
||||
|
||||
const LuaStorage::Value& LuaStorage::Section::get(std::string_view key) const
|
||||
{
|
||||
checkIfActive();
|
||||
auto it = mValues.find(key);
|
||||
if (it != mValues.end())
|
||||
return it->second;
|
||||
@ -49,6 +50,14 @@ namespace LuaUtil
|
||||
return !valid;
|
||||
}),
|
||||
mCallbacks.end());
|
||||
mMenuScriptsCallbacks.erase(std::remove_if(mMenuScriptsCallbacks.begin(), mMenuScriptsCallbacks.end(),
|
||||
[&](const Callback& callback) {
|
||||
bool valid = callback.isValid();
|
||||
if (valid)
|
||||
callback.tryCall(mSectionName, changedKey);
|
||||
return !valid;
|
||||
}),
|
||||
mMenuScriptsCallbacks.end());
|
||||
mStorage->mRunningCallbacks.erase(this);
|
||||
}
|
||||
|
||||
@ -64,6 +73,7 @@ namespace LuaUtil
|
||||
|
||||
void LuaStorage::Section::set(std::string_view key, const sol::object& value)
|
||||
{
|
||||
checkIfActive();
|
||||
throwIfCallbackRecursionIsTooDeep();
|
||||
if (value != sol::nil)
|
||||
mValues[std::string(key)] = Value(value);
|
||||
@ -80,6 +90,7 @@ namespace LuaUtil
|
||||
|
||||
void LuaStorage::Section::setAll(const sol::optional<sol::table>& values)
|
||||
{
|
||||
checkIfActive();
|
||||
throwIfCallbackRecursionIsTooDeep();
|
||||
mValues.clear();
|
||||
if (values)
|
||||
@ -94,6 +105,7 @@ namespace LuaUtil
|
||||
|
||||
sol::table LuaStorage::Section::asTable()
|
||||
{
|
||||
checkIfActive();
|
||||
sol::table res(mStorage->mLua, sol::create);
|
||||
for (const auto& [k, v] : mValues)
|
||||
res[k] = v.getCopy(mStorage->mLua);
|
||||
@ -112,7 +124,8 @@ namespace LuaUtil
|
||||
};
|
||||
sview["asTable"] = [](const SectionView& section) { return section.mSection->asTable(); };
|
||||
sview["subscribe"] = [](const SectionView& section, const sol::table& callback) {
|
||||
std::vector<Callback>& callbacks = section.mSection->mCallbacks;
|
||||
std::vector<Callback>& callbacks
|
||||
= section.mForMenuScripts ? section.mSection->mMenuScriptsCallbacks : section.mSection->mCallbacks;
|
||||
if (!callbacks.empty() && callbacks.size() == callbacks.capacity())
|
||||
{
|
||||
callbacks.erase(
|
||||
@ -166,14 +179,29 @@ namespace LuaUtil
|
||||
return LuaUtil::makeReadOnly(res);
|
||||
}
|
||||
|
||||
sol::table LuaStorage::initMenuPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage)
|
||||
{
|
||||
sol::table res(lua, sol::create);
|
||||
res["playerSection"] = [playerStorage](std::string_view section) {
|
||||
return playerStorage->getMutableSection(section, /*forMenuScripts=*/true);
|
||||
};
|
||||
res["globalSection"]
|
||||
= [globalStorage](std::string_view section) { return globalStorage->getReadOnlySection(section); };
|
||||
res["allPlayerSections"] = [playerStorage]() { return playerStorage->getAllSections(); };
|
||||
return LuaUtil::makeReadOnly(res);
|
||||
}
|
||||
|
||||
void LuaStorage::clearTemporaryAndRemoveCallbacks()
|
||||
{
|
||||
auto it = mData.begin();
|
||||
while (it != mData.end())
|
||||
{
|
||||
it->second->mCallbacks.clear();
|
||||
// Note that we don't clear menu callbacks for permanent sections
|
||||
// because starting/loading a game doesn't reset menu scripts.
|
||||
if (!it->second->mPermanent)
|
||||
{
|
||||
it->second->mMenuScriptsCallbacks.clear();
|
||||
it->second->mValues.clear();
|
||||
it = mData.erase(it);
|
||||
}
|
||||
@ -222,6 +250,7 @@ namespace LuaUtil
|
||||
|
||||
const std::shared_ptr<LuaStorage::Section>& LuaStorage::getSection(std::string_view sectionName)
|
||||
{
|
||||
checkIfActive();
|
||||
auto it = mData.find(sectionName);
|
||||
if (it != mData.end())
|
||||
return it->second;
|
||||
@ -231,14 +260,16 @@ namespace LuaUtil
|
||||
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)
|
||||
{
|
||||
checkIfActive();
|
||||
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)
|
||||
{
|
||||
checkIfActive();
|
||||
sol::table res(mLua, sol::create);
|
||||
for (const auto& [sectionName, _] : mData)
|
||||
res[sectionName] = getSection(sectionName, readOnly);
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <sol/sol.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "asyncpackage.hpp"
|
||||
#include "serialization.hpp"
|
||||
@ -17,9 +18,11 @@ namespace LuaUtil
|
||||
static sol::table initGlobalPackage(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 initMenuPackage(lua_State* lua, LuaStorage* globalStorage, LuaStorage* playerStorage);
|
||||
|
||||
explicit LuaStorage(lua_State* lua)
|
||||
: mLua(lua)
|
||||
, mActive(false)
|
||||
{
|
||||
}
|
||||
|
||||
@ -27,8 +30,11 @@ namespace LuaUtil
|
||||
void load(const std::filesystem::path& path);
|
||||
void save(const std::filesystem::path& path) const;
|
||||
|
||||
sol::object getSection(std::string_view sectionName, bool readOnly);
|
||||
sol::object getMutableSection(std::string_view sectionName) { return getSection(sectionName, false); }
|
||||
sol::object getSection(std::string_view sectionName, bool readOnly, bool forMenuScripts = 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::table getAllSections(bool readOnly = false);
|
||||
|
||||
@ -51,6 +57,7 @@ namespace LuaUtil
|
||||
virtual void sectionReplaced(std::string_view section, const sol::optional<sol::table>& values) const = 0;
|
||||
};
|
||||
void setListener(const Listener* listener) { mListener = listener; }
|
||||
void setActive(bool active) { mActive = active; }
|
||||
|
||||
private:
|
||||
class Value
|
||||
@ -87,13 +94,18 @@ namespace LuaUtil
|
||||
std::string mSectionName;
|
||||
std::map<std::string, Value, std::less<>> mValues;
|
||||
std::vector<Callback> mCallbacks;
|
||||
std::vector<Callback> mMenuScriptsCallbacks; // menu callbacks are in a separate vector because we don't
|
||||
// remove them in clear()
|
||||
bool mPermanent = true;
|
||||
static Value sEmpty;
|
||||
|
||||
void checkIfActive() const { mStorage->checkIfActive(); }
|
||||
};
|
||||
struct SectionView
|
||||
{
|
||||
std::shared_ptr<Section> mSection;
|
||||
bool mReadOnly;
|
||||
bool mForMenuScripts = false;
|
||||
};
|
||||
|
||||
const std::shared_ptr<Section>& getSection(std::string_view sectionName);
|
||||
@ -102,6 +114,12 @@ namespace LuaUtil
|
||||
std::map<std::string_view, std::shared_ptr<Section>> mData;
|
||||
const Listener* mListener = nullptr;
|
||||
std::set<const Section*> mRunningCallbacks;
|
||||
bool mActive;
|
||||
void checkIfActive() const
|
||||
{
|
||||
if (!mActive)
|
||||
throw std::logic_error("Trying to access inactive storage");
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -240,7 +240,8 @@ namespace LuaUi
|
||||
}
|
||||
}
|
||||
|
||||
std::map<Element*, std::shared_ptr<Element>> Element::sAllElements;
|
||||
std::map<Element*, std::shared_ptr<Element>> Element::sMenuElements;
|
||||
std::map<Element*, std::shared_ptr<Element>> Element::sGameElements;
|
||||
|
||||
Element::Element(sol::table layout)
|
||||
: mRoot(nullptr)
|
||||
@ -251,13 +252,21 @@ namespace LuaUi
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<Element> Element::make(sol::table layout)
|
||||
std::shared_ptr<Element> Element::make(sol::table layout, bool menu)
|
||||
{
|
||||
std::shared_ptr<Element> ptr(new Element(std::move(layout)));
|
||||
sAllElements[ptr.get()] = ptr;
|
||||
auto& container = menu ? sMenuElements : sGameElements;
|
||||
container[ptr.get()] = ptr;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void Element::erase(Element* element)
|
||||
{
|
||||
element->destroy();
|
||||
sMenuElements.erase(element);
|
||||
sGameElements.erase(element);
|
||||
}
|
||||
|
||||
void Element::create()
|
||||
{
|
||||
assert(!mRoot);
|
||||
@ -303,6 +312,5 @@ namespace LuaUi
|
||||
mRoot = nullptr;
|
||||
mLayout = sol::make_object(mLayout.lua_state(), sol::nil);
|
||||
}
|
||||
sAllElements.erase(this);
|
||||
}
|
||||
}
|
||||
|
@ -7,13 +7,15 @@ namespace LuaUi
|
||||
{
|
||||
struct Element
|
||||
{
|
||||
static std::shared_ptr<Element> make(sol::table layout);
|
||||
static std::shared_ptr<Element> make(sol::table layout, bool menu);
|
||||
static void erase(Element* element);
|
||||
|
||||
template <class Callback>
|
||||
static void forEach(Callback callback)
|
||||
static void forEach(bool menu, Callback callback)
|
||||
{
|
||||
for (auto& [e, _] : sAllElements)
|
||||
callback(e);
|
||||
auto& container = menu ? sMenuElements : sGameElements;
|
||||
for (auto& [_, element] : container)
|
||||
callback(element.get());
|
||||
}
|
||||
|
||||
WidgetExtension* mRoot;
|
||||
@ -28,12 +30,14 @@ namespace LuaUi
|
||||
|
||||
void destroy();
|
||||
|
||||
friend void clearUserInterface();
|
||||
friend void clearGameInterface();
|
||||
friend void clearMenuInterface();
|
||||
|
||||
private:
|
||||
Element(sol::table layout);
|
||||
sol::table layout() { return LuaUtil::cast<sol::table>(mLayout); }
|
||||
static std::map<Element*, std::shared_ptr<Element>> sAllElements;
|
||||
static std::map<Element*, std::shared_ptr<Element>> sGameElements;
|
||||
static std::map<Element*, std::shared_ptr<Element>> sMenuElements;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ namespace LuaUi
|
||||
// implemented in scriptsettings.cpp
|
||||
void registerSettingsPage(const sol::table& options);
|
||||
void clearSettings();
|
||||
void removeSettingsPage(const sol::table& options);
|
||||
}
|
||||
|
||||
#endif // !OPENMW_LUAUI_REGISTERSCRIPTSETTINGS
|
||||
|
@ -40,6 +40,11 @@ namespace LuaUi
|
||||
allPages.push_back(options);
|
||||
}
|
||||
|
||||
void removeSettingsPage(const sol::table& options)
|
||||
{
|
||||
std::erase_if(allPages, [options](const sol::table& it) { return it == options; });
|
||||
}
|
||||
|
||||
void clearSettings()
|
||||
{
|
||||
allPages.clear();
|
||||
@ -47,10 +52,10 @@ namespace LuaUi
|
||||
|
||||
void attachPageAt(size_t index, LuaAdapter* adapter)
|
||||
{
|
||||
adapter->detach();
|
||||
if (index < allPages.size())
|
||||
{
|
||||
ScriptSettingsPage page = parse(allPages[index]);
|
||||
adapter->detach();
|
||||
if (page.mElement.get())
|
||||
adapter->attach(page.mElement);
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace LuaUi
|
||||
{
|
||||
|
@ -11,6 +11,9 @@ namespace LuaUi
|
||||
{
|
||||
MYGUI_RTTI_DERIVED(LuaTextEdit)
|
||||
|
||||
public:
|
||||
bool isTextInput() override { return mEditBox->getEditStatic(); }
|
||||
|
||||
protected:
|
||||
void initialize() override;
|
||||
void deinitialize() override;
|
||||
|
@ -44,10 +44,15 @@ namespace LuaUi
|
||||
return types;
|
||||
}
|
||||
|
||||
void clearUserInterface()
|
||||
void clearGameInterface()
|
||||
{
|
||||
clearSettings();
|
||||
while (!Element::sAllElements.empty())
|
||||
Element::sAllElements.begin()->second->destroy();
|
||||
while (!Element::sGameElements.empty())
|
||||
Element::erase(Element::sGameElements.begin()->second.get());
|
||||
}
|
||||
|
||||
void clearMenuInterface()
|
||||
{
|
||||
while (!Element::sMenuElements.empty())
|
||||
Element::erase(Element::sMenuElements.begin()->second.get());
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,8 @@ namespace LuaUi
|
||||
|
||||
const std::unordered_map<std::string, std::string>& widgetTypeToName();
|
||||
|
||||
void clearUserInterface();
|
||||
void clearGameInterface();
|
||||
void clearMenuInterface();
|
||||
}
|
||||
|
||||
#endif // OPENMW_LUAUI_WIDGETLIST
|
||||
|
@ -73,6 +73,8 @@ namespace LuaUi
|
||||
virtual MyGUI::IntPoint calculatePosition(const MyGUI::IntSize& size);
|
||||
MyGUI::IntCoord calculateCoord();
|
||||
|
||||
virtual bool isTextInput() { return false; }
|
||||
|
||||
protected:
|
||||
virtual void initialize();
|
||||
void registerEvents(MyGUI::Widget* w);
|
||||
|
@ -2,8 +2,8 @@ paths=(
|
||||
openmw_aux/*lua
|
||||
scripts/omw/activationhandlers.lua
|
||||
scripts/omw/ai.lua
|
||||
scripts/omw/input/playercontrols.lua
|
||||
scripts/omw/mechanics/animationcontroller.lua
|
||||
scripts/omw/playercontrols.lua
|
||||
scripts/omw/camera/camera.lua
|
||||
scripts/omw/mwui/init.lua
|
||||
scripts/omw/settings/player.lua
|
||||
|
@ -28,6 +28,7 @@ Lua API reference
|
||||
openmw_camera
|
||||
openmw_postprocessing
|
||||
openmw_debug
|
||||
openmw_menu
|
||||
openmw_aux_calendar
|
||||
openmw_aux_util
|
||||
openmw_aux_time
|
||||
|
@ -5,10 +5,17 @@ Engine handlers reference
|
||||
|
||||
Engine handler is a function defined by a script, that can be called by the engine.
|
||||
|
||||
|
||||
|
||||
**Can be defined by any script**
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
|
||||
* - onInterfaceOverride(base)
|
||||
- | Called if the current script has an interface and overrides an interface
|
||||
| (``base``) of another script.
|
||||
|
||||
**Can be defined by any non-menu script**
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
|
||||
@ -29,9 +36,6 @@ Engine handler is a function defined by a script, that can be called by the engi
|
||||
| Note that ``onLoad`` means loading a script rather than loading a game.
|
||||
| If a script did not exist when a game was saved onLoad will not be
|
||||
| called, but ``onInit`` will.
|
||||
* - onInterfaceOverride(base)
|
||||
- | Called if the current script has an interface and overrides an interface
|
||||
| (``base``) of another script.
|
||||
|
||||
**Only for global scripts**
|
||||
|
||||
@ -80,7 +84,7 @@ Engine handler is a function defined by a script, that can be called by the engi
|
||||
| Similarly to onActivated, the item has already been removed
|
||||
| from the actor's inventory, and the count was set to zero.
|
||||
|
||||
**Only for local scripts attached to a player**
|
||||
**Only menu scripts and local scripts attached to a player**
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
@ -94,8 +98,6 @@ Engine handler is a function defined by a script, that can be called by the engi
|
||||
- | `Key <openmw_input.html##(KeyboardEvent)>`_ is pressed.
|
||||
| Usage example:
|
||||
| ``if key.symbol == 'z' and key.withShift then ...``
|
||||
* - onQuestUpdate(questId, stage)
|
||||
- | Called when a quest is updated.
|
||||
* - onKeyRelease(key)
|
||||
- | `Key <openmw_input.html##(KeyboardEvent)>`_ is released.
|
||||
| Usage example:
|
||||
@ -127,3 +129,23 @@ Engine handler is a function defined by a script, that can be called by the engi
|
||||
- | User entered `command` in in-game console. Called if either
|
||||
| `mode` is not default or `command` starts with prefix `lua`.
|
||||
|
||||
**Only for local scripts attached to a player**
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
|
||||
* - onKeyPress(key)
|
||||
- | `Key <openmw_input.html##(KeyboardEvent)>`_ is pressed.
|
||||
| Usage example:
|
||||
| ``if key.symbol == 'z' and key.withShift then ...``
|
||||
* - onQuestUpdate(questId, stage)
|
||||
- | Called when a quest is updated.
|
||||
|
||||
**Only for menu scripts**
|
||||
|
||||
.. list-table::
|
||||
:widths: 20 80
|
||||
|
||||
* - onStateChanged()
|
||||
- | Called whenever the current game changes
|
||||
| (i. e. the result of `getState <openmw_menu.html##(getState)>`_ changes)
|
||||
|
7
docs/source/reference/lua-scripting/openmw_menu.rst
Normal file
7
docs/source/reference/lua-scripting/openmw_menu.rst
Normal file
@ -0,0 +1,7 @@
|
||||
Package openmw.menu
|
||||
======================
|
||||
|
||||
.. include:: version.rst
|
||||
|
||||
.. raw:: html
|
||||
:file: generated_html/openmw_menu.html
|
@ -70,6 +70,9 @@ Cell
|
||||
Global scripts
|
||||
Lua scripts that are not attached to any game object and are always active. Global scripts can not be started or stopped during a game session. Lists of global scripts are defined by `omwscripts` files, which should be :ref:`registered <Lua scripting>` in `openmw.cfg`.
|
||||
|
||||
Menu scripts
|
||||
Lua scripts that are ran regardless of a game being loaded. They can be used to add features to the main menu and manage save files.
|
||||
|
||||
Local scripts
|
||||
Lua scripts that are attached to some game object. A local script is active only if the object it is attached to is in an active cell. There are no limitations to the number of local scripts on one object. Local scripts can be attached to (or detached from) any object at any moment by a global script. In some cases inactive local scripts still can run code (for example during saving and loading), but while inactive they can not see nearby objects.
|
||||
|
||||
@ -173,6 +176,7 @@ The order of lines determines the script load order (i.e. script priorities).
|
||||
Possible flags are:
|
||||
|
||||
- ``GLOBAL`` - a global script; always active, can not be stopped;
|
||||
- ``MENU`` - a menu script; always active, even before a game is loaded
|
||||
- ``CUSTOM`` - dynamic local script that can be started or stopped by a global script;
|
||||
- ``PLAYER`` - an auto started player script;
|
||||
- ``ACTIVATOR`` - a local script that will be automatically attached to any activator;
|
||||
@ -474,6 +478,12 @@ This is another kind of script-to-script interactions. The differences:
|
||||
- Event handlers can not return any data to the sender.
|
||||
- Event handlers have a single argument `eventData` (must be :ref:`serializable <Serializable data>`)
|
||||
|
||||
There are a few methods for sending events:
|
||||
|
||||
- `core.sendGlobalEvent <openmw_core.html##(sendGlobalEvent)>`_ to send events to global scripts
|
||||
- `GameObject:sendEvent <openmw_core.html##(GameObject).sendEvent>`_ to send events to local scripts attached to a game object
|
||||
- `types.Player.sendMenuEvent <openmw_menu.html##(Player).sendMenuEvent>`_ to send events to menu scripts of the given player
|
||||
|
||||
Events are the main way of interacting between local and global scripts.
|
||||
They are not recommended for interactions between two global scripts, because in this case interfaces are more convenient.
|
||||
|
||||
@ -614,7 +624,7 @@ Also in `openmw_aux`_ is the helper function ``runRepeatedly``, it is implemente
|
||||
local core = require('openmw.core')
|
||||
local time = require('openmw_aux.time')
|
||||
|
||||
-- call `doSomething()` at the end of every game day.
|
||||
-- call `doSomething()` at the end of every game day.
|
||||
-- the second argument (`time.day`) is the interval.
|
||||
-- the periodical evaluation can be stopped at any moment by calling `stopFn()`
|
||||
local timeBeforeMidnight = time.day - core.getGameTime() % time.day
|
||||
|
@ -31,6 +31,8 @@
|
||||
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.ui <Package openmw.ui>` | by player scripts | | Controls :ref:`user interface <User interface reference>`. |
|
||||
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.menu <Package openmw.menu>` | by menu scripts | | Main menu functionality, such as managing game saves |
|
||||
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.camera <Package openmw.camera>` | by player scripts | | Controls camera. |
|
||||
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.postprocessing <Package openmw.postprocessing>`| by player scripts | | Controls post-process shaders. |
|
||||
|
@ -72,16 +72,16 @@ set(BUILTIN_DATA_FILES
|
||||
scripts/omw/camera/settings.lua
|
||||
scripts/omw/camera/move360.lua
|
||||
scripts/omw/camera/first_person_auto_switch.lua
|
||||
scripts/omw/console/player.lua
|
||||
scripts/omw/console/global.lua
|
||||
scripts/omw/console/local.lua
|
||||
scripts/omw/console/player.lua
|
||||
scripts/omw/console/menu.lua
|
||||
scripts/omw/mechanics/animationcontroller.lua
|
||||
scripts/omw/mechanics/playercontroller.lua
|
||||
scripts/omw/playercontrols.lua
|
||||
scripts/omw/settings/menu.lua
|
||||
scripts/omw/settings/player.lua
|
||||
scripts/omw/settings/global.lua
|
||||
scripts/omw/settings/common.lua
|
||||
scripts/omw/settings/render.lua
|
||||
scripts/omw/settings/renderers.lua
|
||||
scripts/omw/mwui/constants.lua
|
||||
scripts/omw/mwui/borders.lua
|
||||
@ -93,6 +93,8 @@ set(BUILTIN_DATA_FILES
|
||||
scripts/omw/ui.lua
|
||||
scripts/omw/usehandlers.lua
|
||||
scripts/omw/worldeventhandlers.lua
|
||||
scripts/omw/input/settings.lua
|
||||
scripts/omw/input/playercontrols.lua
|
||||
scripts/omw/input/actionbindings.lua
|
||||
scripts/omw/input/smoothmovement.lua
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
# UI framework
|
||||
PLAYER: scripts/omw/mwui/init.lua
|
||||
MENU,PLAYER: scripts/omw/mwui/init.lua
|
||||
|
||||
# Settings framework
|
||||
GLOBAL: scripts/omw/settings/global.lua
|
||||
MENU: scripts/omw/settings/menu.lua
|
||||
PLAYER: scripts/omw/settings/player.lua
|
||||
GLOBAL: scripts/omw/settings/global.lua
|
||||
|
||||
# Mechanics
|
||||
GLOBAL: scripts/omw/activationhandlers.lua
|
||||
@ -12,8 +13,10 @@ GLOBAL: scripts/omw/usehandlers.lua
|
||||
GLOBAL: scripts/omw/worldeventhandlers.lua
|
||||
CREATURE, NPC, PLAYER: scripts/omw/mechanics/animationcontroller.lua
|
||||
PLAYER: scripts/omw/mechanics/playercontroller.lua
|
||||
PLAYER: scripts/omw/playercontrols.lua
|
||||
MENU: scripts/omw/camera/settings.lua
|
||||
PLAYER: scripts/omw/camera/camera.lua
|
||||
MENU: scripts/omw/input/settings.lua
|
||||
PLAYER: scripts/omw/input/playercontrols.lua
|
||||
PLAYER: scripts/omw/input/actionbindings.lua
|
||||
PLAYER: scripts/omw/input/smoothmovement.lua
|
||||
NPC,CREATURE: scripts/omw/ai.lua
|
||||
@ -22,6 +25,7 @@ NPC,CREATURE: scripts/omw/ai.lua
|
||||
PLAYER: scripts/omw/ui.lua
|
||||
|
||||
# Lua console
|
||||
MENU: scripts/omw/console/menu.lua
|
||||
PLAYER: scripts/omw/console/player.lua
|
||||
GLOBAL: scripts/omw/console/global.lua
|
||||
CUSTOM: scripts/omw/console/local.lua
|
||||
|
@ -5,6 +5,7 @@ local util = require('openmw.util')
|
||||
local self = require('openmw.self')
|
||||
local nearby = require('openmw.nearby')
|
||||
local async = require('openmw.async')
|
||||
local storage = require('openmw.storage')
|
||||
local I = require('openmw.interfaces')
|
||||
|
||||
local Actor = require('openmw.types').Actor
|
||||
@ -28,7 +29,7 @@ input.registerAction {
|
||||
defaultValue = 0,
|
||||
}
|
||||
|
||||
local settings = require('scripts.omw.camera.settings').thirdPerson
|
||||
local settings = storage.playerSection('SettingsOMWCameraThirdPerson')
|
||||
local head_bobbing = require('scripts.omw.camera.head_bobbing')
|
||||
local third_person = require('scripts.omw.camera.third_person')
|
||||
local pov_auto_switch = require('scripts.omw.camera.first_person_auto_switch')
|
||||
|
@ -2,12 +2,13 @@ local camera = require('openmw.camera')
|
||||
local self = require('openmw.self')
|
||||
local util = require('openmw.util')
|
||||
local async = require('openmw.async')
|
||||
local storage = require('openmw.storage')
|
||||
|
||||
local Actor = require('openmw.types').Actor
|
||||
|
||||
local M = {}
|
||||
|
||||
local settings = require('scripts.omw.camera.settings').headBobbing
|
||||
local settings = storage.playerSection('SettingsOMWCameraHeadBobbing')
|
||||
|
||||
local doubleStepLength, stepHeight, maxRoll
|
||||
|
||||
@ -31,7 +32,7 @@ local arcHeight = sampleArc(1)
|
||||
|
||||
function M.update(dt, smoothedSpeed)
|
||||
local speed = Actor.getCurrentSpeed(self)
|
||||
speed = speed / (1 + speed / 500) -- limit bobbing frequency if the speed is very high
|
||||
speed = speed / (1 + speed / 500) -- limit bobbing frequency if the speed is very high
|
||||
totalMovement = totalMovement + speed * dt
|
||||
if not M.enabled or camera.getMode() ~= camera.MODE.FirstPerson then
|
||||
effectWeight = 0
|
||||
@ -44,18 +45,17 @@ function M.update(dt, smoothedSpeed)
|
||||
end
|
||||
|
||||
local doubleStepState = totalMovement / doubleStepLength
|
||||
doubleStepState = doubleStepState - math.floor(doubleStepState) -- from 0 to 1 during 2 steps
|
||||
local stepState = math.abs(doubleStepState * 4 - 2) - 1 -- from -1 to 1 on even steps and from 1 to -1 on odd steps
|
||||
local effect = sampleArc(stepState) / arcHeight -- range from 0 to 1
|
||||
doubleStepState = doubleStepState - math.floor(doubleStepState) -- from 0 to 1 during 2 steps
|
||||
local stepState = math.abs(doubleStepState * 4 - 2) - 1 -- from -1 to 1 on even steps and from 1 to -1 on odd steps
|
||||
local effect = sampleArc(stepState) / arcHeight -- range from 0 to 1
|
||||
|
||||
-- Smoothly reduce the effect to zero when the player stops
|
||||
local coef = math.min(smoothedSpeed / 300, 1) * effectWeight
|
||||
|
||||
local zOffset = (0.5 - effect) * coef * stepHeight -- range from -stepHeight/2 to stepHeight/2
|
||||
local roll = ((stepState > 0 and 1) or -1) * effect * coef * maxRoll -- range from -maxRoll to maxRoll
|
||||
local zOffset = (0.5 - effect) * coef * stepHeight -- range from -stepHeight/2 to stepHeight/2
|
||||
local roll = ((stepState > 0 and 1) or -1) * effect * coef * maxRoll -- range from -maxRoll to maxRoll
|
||||
camera.setFirstPersonOffset(camera.getFirstPersonOffset() + util.vector3(0, 0, zOffset))
|
||||
camera.setExtraRoll(camera.getExtraRoll() + roll)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
|
@ -3,10 +3,10 @@ local async = require('openmw.async')
|
||||
local I = require('openmw.interfaces')
|
||||
|
||||
I.Settings.registerPage({
|
||||
key = 'OMWCamera',
|
||||
l10n = 'OMWCamera',
|
||||
name = 'Camera',
|
||||
description = 'settingsPageDescription',
|
||||
key = 'OMWCamera',
|
||||
l10n = 'OMWCamera',
|
||||
name = 'Camera',
|
||||
description = 'settingsPageDescription',
|
||||
})
|
||||
|
||||
local thirdPersonGroup = 'SettingsOMWCameraThirdPerson'
|
||||
@ -16,8 +16,8 @@ local function boolSetting(prefix, key, default)
|
||||
return {
|
||||
key = key,
|
||||
renderer = 'checkbox',
|
||||
name = prefix..key,
|
||||
description = prefix..key..'Description',
|
||||
name = prefix .. key,
|
||||
description = prefix .. key .. 'Description',
|
||||
default = default,
|
||||
}
|
||||
end
|
||||
@ -26,8 +26,8 @@ local function floatSetting(prefix, key, default)
|
||||
return {
|
||||
key = key,
|
||||
renderer = 'number',
|
||||
name = prefix..key,
|
||||
description = prefix..key..'Description',
|
||||
name = prefix .. key,
|
||||
description = prefix .. key .. 'Description',
|
||||
default = default,
|
||||
}
|
||||
end
|
||||
@ -70,33 +70,29 @@ I.Settings.registerGroup({
|
||||
},
|
||||
})
|
||||
|
||||
local settings = {
|
||||
thirdPerson = storage.playerSection(thirdPersonGroup),
|
||||
headBobbing = storage.playerSection(headBobbingGroup),
|
||||
}
|
||||
local thirdPerson = storage.playerSection(thirdPersonGroup)
|
||||
local headBobbing = storage.playerSection(headBobbingGroup)
|
||||
|
||||
local function updateViewOverShoulderDisabled()
|
||||
local shoulderDisabled = not settings.thirdPerson:get('viewOverShoulder')
|
||||
I.Settings.updateRendererArgument(thirdPersonGroup, 'shoulderOffsetX', {disabled = shoulderDisabled})
|
||||
I.Settings.updateRendererArgument(thirdPersonGroup, 'shoulderOffsetY', {disabled = shoulderDisabled})
|
||||
I.Settings.updateRendererArgument(thirdPersonGroup, 'autoSwitchShoulder', {disabled = shoulderDisabled})
|
||||
I.Settings.updateRendererArgument(thirdPersonGroup, 'zoomOutWhenMoveCoef', {disabled = shoulderDisabled})
|
||||
local shoulderDisabled = not thirdPerson:get('viewOverShoulder')
|
||||
I.Settings.updateRendererArgument(thirdPersonGroup, 'shoulderOffsetX', { disabled = shoulderDisabled })
|
||||
I.Settings.updateRendererArgument(thirdPersonGroup, 'shoulderOffsetY', { disabled = shoulderDisabled })
|
||||
I.Settings.updateRendererArgument(thirdPersonGroup, 'autoSwitchShoulder', { disabled = shoulderDisabled })
|
||||
I.Settings.updateRendererArgument(thirdPersonGroup, 'zoomOutWhenMoveCoef', { disabled = shoulderDisabled })
|
||||
|
||||
local move360Disabled = not settings.thirdPerson:get('move360')
|
||||
I.Settings.updateRendererArgument(thirdPersonGroup, 'move360TurnSpeed', {disabled = move360Disabled})
|
||||
local move360Disabled = not thirdPerson:get('move360')
|
||||
I.Settings.updateRendererArgument(thirdPersonGroup, 'move360TurnSpeed', { disabled = move360Disabled })
|
||||
end
|
||||
|
||||
local function updateHeadBobbingDisabled()
|
||||
local disabled = not settings.headBobbing:get('enabled')
|
||||
I.Settings.updateRendererArgument(headBobbingGroup, 'step', {disabled = disabled, min = 1})
|
||||
I.Settings.updateRendererArgument(headBobbingGroup, 'height', {disabled = disabled})
|
||||
I.Settings.updateRendererArgument(headBobbingGroup, 'roll', {disabled = disabled, min = 0, max = 90})
|
||||
local disabled = not headBobbing:get('enabled')
|
||||
I.Settings.updateRendererArgument(headBobbingGroup, 'step', { disabled = disabled, min = 1 })
|
||||
I.Settings.updateRendererArgument(headBobbingGroup, 'height', { disabled = disabled })
|
||||
I.Settings.updateRendererArgument(headBobbingGroup, 'roll', { disabled = disabled, min = 0, max = 90 })
|
||||
end
|
||||
|
||||
updateViewOverShoulderDisabled()
|
||||
updateHeadBobbingDisabled()
|
||||
|
||||
settings.thirdPerson:subscribe(async:callback(updateViewOverShoulderDisabled))
|
||||
settings.headBobbing:subscribe(async:callback(updateHeadBobbingDisabled))
|
||||
|
||||
return settings
|
||||
thirdPerson:subscribe(async:callback(updateViewOverShoulderDisabled))
|
||||
headBobbing:subscribe(async:callback(updateHeadBobbingDisabled))
|
||||
|
@ -3,10 +3,11 @@ local util = require('openmw.util')
|
||||
local self = require('openmw.self')
|
||||
local nearby = require('openmw.nearby')
|
||||
local async = require('openmw.async')
|
||||
local storage = require('openmw.storage')
|
||||
|
||||
local Actor = require('openmw.types').Actor
|
||||
|
||||
local settings = require('scripts.omw.camera.settings').thirdPerson
|
||||
local settings = storage.playerSection('SettingsOMWCameraThirdPerson')
|
||||
|
||||
local MODE = camera.MODE
|
||||
local STATE = { RightShoulder = 0, LeftShoulder = 1, Combat = 2, Swimming = 3 }
|
||||
@ -31,7 +32,7 @@ local function updateSettings()
|
||||
viewOverShoulder = settings:get('viewOverShoulder')
|
||||
autoSwitchShoulder = settings:get('autoSwitchShoulder')
|
||||
shoulderOffset = util.vector2(settings:get('shoulderOffsetX'),
|
||||
settings:get('shoulderOffsetY'))
|
||||
settings:get('shoulderOffsetY'))
|
||||
zoomOutWhenMoveCoef = settings:get('zoomOutWhenMoveCoef')
|
||||
|
||||
defaultShoulder = (shoulderOffset.x > 0 and STATE.RightShoulder) or STATE.LeftShoulder
|
||||
@ -46,7 +47,7 @@ local state = defaultShoulder
|
||||
|
||||
local function ray(from, angle, limit)
|
||||
local to = from + util.transform.rotateZ(angle) * util.vector3(0, limit, 0)
|
||||
local res = nearby.castRay(from, to, {collisionType = camera.getCollisionType()})
|
||||
local res = nearby.castRay(from, to, { collisionType = camera.getCollisionType() })
|
||||
if res.hit then
|
||||
return (res.hitPos - from):length()
|
||||
else
|
||||
@ -55,8 +56,8 @@ local function ray(from, angle, limit)
|
||||
end
|
||||
|
||||
local function trySwitchShoulder()
|
||||
local limitToSwitch = 120 -- switch to other shoulder if wall is closer than this limit
|
||||
local limitToSwitchBack = 300 -- switch back to default shoulder if there is no walls at this distance
|
||||
local limitToSwitch = 120 -- switch to other shoulder if wall is closer than this limit
|
||||
local limitToSwitchBack = 300 -- switch back to default shoulder if there is no walls at this distance
|
||||
|
||||
local pos = camera.getTrackedPosition()
|
||||
local rayRight = ray(pos, camera.getYaw() + math.rad(90), limitToSwitchBack + 1)
|
||||
@ -79,7 +80,7 @@ end
|
||||
local function calculateDistance(smoothedSpeed)
|
||||
local smoothedSpeedSqr = smoothedSpeed * smoothedSpeed
|
||||
return (M.baseDistance + math.max(camera.getPitch(), 0) * 50
|
||||
+ smoothedSpeedSqr / (smoothedSpeedSqr + 300*300) * zoomOutWhenMoveCoef)
|
||||
+ smoothedSpeedSqr / (smoothedSpeedSqr + 300 * 300) * zoomOutWhenMoveCoef)
|
||||
end
|
||||
|
||||
local function updateState()
|
||||
@ -95,7 +96,7 @@ local function updateState()
|
||||
state = defaultShoulder
|
||||
end
|
||||
if (mode == MODE.ThirdPerson or Actor.getCurrentSpeed(self) > 0 or state ~= oldState or noThirdPersonLastFrame)
|
||||
and (state == STATE.LeftShoulder or state == STATE.RightShoulder) then
|
||||
and (state == STATE.LeftShoulder or state == STATE.RightShoulder) then
|
||||
if autoSwitchShoulder then
|
||||
trySwitchShoulder()
|
||||
else
|
||||
@ -108,11 +109,11 @@ local function updateState()
|
||||
-- Player doesn't touch controls for a long time. Transition should be very slow.
|
||||
camera.setFocalTransitionSpeed(0.2)
|
||||
elseif (oldState == STATE.Combat or state == STATE.Combat) and
|
||||
(mode ~= MODE.Preview or M.standingPreview) then
|
||||
(mode ~= MODE.Preview or M.standingPreview) then
|
||||
-- Transition to/from combat mode and we are not in preview mode. Should be fast.
|
||||
camera.setFocalTransitionSpeed(5.0)
|
||||
else
|
||||
camera.setFocalTransitionSpeed(1.0) -- Default transition speed.
|
||||
camera.setFocalTransitionSpeed(1.0) -- Default transition speed.
|
||||
end
|
||||
|
||||
if state == STATE.RightShoulder then
|
||||
@ -149,7 +150,7 @@ function M.update(dt, smoothedSpeed)
|
||||
end
|
||||
|
||||
M.preferredDistance = calculateDistance(smoothedSpeed)
|
||||
if noThirdPersonLastFrame then -- just switched to third person view
|
||||
if noThirdPersonLastFrame then -- just switched to third person view
|
||||
camera.setPreferredThirdPersonDistance(M.preferredDistance)
|
||||
camera.instantTransition()
|
||||
noThirdPersonLastFrame = false
|
||||
@ -161,4 +162,3 @@ function M.update(dt, smoothedSpeed)
|
||||
end
|
||||
|
||||
return M
|
||||
|
||||
|
115
files/data/scripts/omw/console/menu.lua
Normal file
115
files/data/scripts/omw/console/menu.lua
Normal file
@ -0,0 +1,115 @@
|
||||
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'),
|
||||
input = require('openmw.input'),
|
||||
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'),
|
||||
self = require('openmw.self'),
|
||||
input = require('openmw.input'),
|
||||
postprocessing = require('openmw.postprocessing'),
|
||||
ui = require('openmw.ui'),
|
||||
camera = require('openmw.camera'),
|
||||
aux_util = require('openmw_aux.util'),
|
||||
@ -114,9 +115,12 @@ local function onConsoleCommand(mode, cmd, selectedObject)
|
||||
cmd = 'luag'
|
||||
elseif arg == 'selected' then
|
||||
cmd = 'luas'
|
||||
elseif arg == 'menu' then
|
||||
-- handled in menu.lua
|
||||
else
|
||||
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 selected' or 'luas' - enter local context on the selected object]]
|
||||
ui.printToConsole(msg, ui.CONSOLE_COLOR.Info)
|
||||
@ -158,4 +162,3 @@ return {
|
||||
OMWConsoleHelp = printHelp,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,90 +134,16 @@ function clearBinding(id)
|
||||
end
|
||||
end
|
||||
|
||||
local function updateBinding(id, binding)
|
||||
bindingSection:set(id, binding)
|
||||
bindingSection:subscribe(async:callback(function(_, id)
|
||||
if not id then return end
|
||||
local binding = bindingSection:get(id)
|
||||
clearBinding(id)
|
||||
if binding ~= nil then
|
||||
registerBinding(binding, id)
|
||||
end
|
||||
return id
|
||||
end
|
||||
end))
|
||||
|
||||
local interfaceL10n = core.l10n('interface')
|
||||
|
||||
I.Settings.registerRenderer('inputBinding', function(id, set, arg)
|
||||
if type(id) ~= 'string' then error('inputBinding: must have a string default value') end
|
||||
if not arg.type then error('inputBinding: type argument is required') end
|
||||
if not arg.key then error('inputBinding: key argument is required') end
|
||||
local info = input.actions[arg.key] or input.triggers[arg.key]
|
||||
if not info then return {} end
|
||||
|
||||
local l10n = core.l10n(info.key)
|
||||
|
||||
local name = {
|
||||
template = I.MWUI.templates.textNormal,
|
||||
props = {
|
||||
text = l10n(info.name),
|
||||
},
|
||||
}
|
||||
|
||||
local description = {
|
||||
template = I.MWUI.templates.textNormal,
|
||||
props = {
|
||||
text = l10n(info.description),
|
||||
},
|
||||
}
|
||||
|
||||
local binding = bindingSection:get(id)
|
||||
local label = binding and input.getKeyName(binding.code) or interfaceL10n('None')
|
||||
|
||||
local recorder = {
|
||||
template = I.MWUI.templates.textEditLine,
|
||||
props = {
|
||||
readOnly = true,
|
||||
text = label,
|
||||
},
|
||||
events = {
|
||||
focusGain = async:callback(function()
|
||||
if binding == nil then return end
|
||||
updateBinding(id, nil)
|
||||
set(id)
|
||||
end),
|
||||
keyPress = async:callback(function(key)
|
||||
if binding ~= nil or key.code == input.KEY.Escape then return end
|
||||
|
||||
local newBinding = {
|
||||
code = key.code,
|
||||
type = arg.type,
|
||||
key = arg.key,
|
||||
}
|
||||
updateBinding(id, newBinding)
|
||||
set(id)
|
||||
end),
|
||||
},
|
||||
}
|
||||
|
||||
local row = {
|
||||
type = ui.TYPE.Flex,
|
||||
props = {
|
||||
horizontal = true,
|
||||
},
|
||||
content = ui.content {
|
||||
name,
|
||||
{ props = { size = util.vector2(10, 0) } },
|
||||
recorder,
|
||||
},
|
||||
}
|
||||
local column = {
|
||||
type = ui.TYPE.Flex,
|
||||
content = ui.content {
|
||||
row,
|
||||
description,
|
||||
},
|
||||
}
|
||||
|
||||
return column
|
||||
end)
|
||||
|
||||
local initiated = false
|
||||
|
||||
|
@ -9,38 +9,6 @@ local Player = require('openmw.types').Player
|
||||
|
||||
local I = require('openmw.interfaces')
|
||||
|
||||
local settingsGroup = 'SettingsOMWControls'
|
||||
|
||||
local function boolSetting(key, default)
|
||||
return {
|
||||
key = key,
|
||||
renderer = 'checkbox',
|
||||
name = key,
|
||||
description = key .. 'Description',
|
||||
default = default,
|
||||
}
|
||||
end
|
||||
|
||||
I.Settings.registerPage({
|
||||
key = 'OMWControls',
|
||||
l10n = 'OMWControls',
|
||||
name = 'ControlsPage',
|
||||
description = 'ControlsPageDescription',
|
||||
})
|
||||
|
||||
I.Settings.registerGroup({
|
||||
key = settingsGroup,
|
||||
page = 'OMWControls',
|
||||
l10n = 'OMWControls',
|
||||
name = 'MovementSettings',
|
||||
permanentStorage = true,
|
||||
settings = {
|
||||
boolSetting('alwaysRun', false),
|
||||
boolSetting('toggleSneak', false), -- TODO: consider removing this setting when we have the advanced binding UI
|
||||
boolSetting('smoothControllerMovement', true),
|
||||
},
|
||||
})
|
||||
|
||||
local settings = storage.playerSection('SettingsOMWControls')
|
||||
|
||||
do
|
136
files/data/scripts/omw/input/settings.lua
Normal file
136
files/data/scripts/omw/input/settings.lua
Normal file
@ -0,0 +1,136 @@
|
||||
local core = require('openmw.core')
|
||||
local input = require('openmw.input')
|
||||
local storage = require('openmw.storage')
|
||||
local ui = require('openmw.ui')
|
||||
local util = require('openmw.util')
|
||||
local async = require('openmw.async')
|
||||
local I = require('openmw.interfaces')
|
||||
|
||||
local settingsGroup = 'SettingsOMWControls'
|
||||
|
||||
local function boolSetting(key, default)
|
||||
return {
|
||||
key = key,
|
||||
renderer = 'checkbox',
|
||||
name = key,
|
||||
description = key .. 'Description',
|
||||
default = default,
|
||||
}
|
||||
end
|
||||
|
||||
I.Settings.registerPage({
|
||||
key = 'OMWControls',
|
||||
l10n = 'OMWControls',
|
||||
name = 'ControlsPage',
|
||||
description = 'ControlsPageDescription',
|
||||
})
|
||||
|
||||
I.Settings.registerGroup({
|
||||
key = settingsGroup,
|
||||
page = 'OMWControls',
|
||||
l10n = 'OMWControls',
|
||||
name = 'MovementSettings',
|
||||
permanentStorage = true,
|
||||
settings = {
|
||||
boolSetting('alwaysRun', false),
|
||||
boolSetting('toggleSneak', false), -- TODO: consider removing this setting when we have the advanced binding UI
|
||||
boolSetting('smoothControllerMovement', true),
|
||||
},
|
||||
})
|
||||
|
||||
local interfaceL10n = core.l10n('interface')
|
||||
|
||||
local bindingSection = storage.playerSection('OMWInputBindings')
|
||||
|
||||
local recording = nil
|
||||
|
||||
|
||||
I.Settings.registerRenderer('inputBinding', function(id, set, arg)
|
||||
if type(id) ~= 'string' then error('inputBinding: must have a string default value') end
|
||||
if not arg then error('inputBinding: argument with "key" and "type" is required') end
|
||||
if not arg.type then error('inputBinding: type argument is required') end
|
||||
if not arg.key then error('inputBinding: key argument is required') end
|
||||
local info = input.actions[arg.key] or input.triggers[arg.key]
|
||||
if not info then return {} end
|
||||
|
||||
local l10n = core.l10n(info.key)
|
||||
|
||||
local name = {
|
||||
template = I.MWUI.templates.textNormal,
|
||||
props = {
|
||||
text = l10n(info.name),
|
||||
},
|
||||
}
|
||||
|
||||
local description = {
|
||||
template = I.MWUI.templates.textNormal,
|
||||
props = {
|
||||
text = l10n(info.description),
|
||||
},
|
||||
}
|
||||
|
||||
local binding = bindingSection:get(id)
|
||||
local label = interfaceL10n('None')
|
||||
if binding then label = input.getKeyName(binding.code) end
|
||||
if recording and recording.id == id then label = interfaceL10n('N/A') end
|
||||
|
||||
local recorder = {
|
||||
template = I.MWUI.templates.textNormal,
|
||||
props = {
|
||||
text = label,
|
||||
},
|
||||
events = {
|
||||
mouseClick = async:callback(function()
|
||||
if recording ~= nil then return end
|
||||
if binding ~= nil then bindingSection:set(id, nil) end
|
||||
recording = {
|
||||
id = id,
|
||||
arg = arg,
|
||||
refresh = function() set(id) end,
|
||||
}
|
||||
recording.refresh()
|
||||
end),
|
||||
},
|
||||
}
|
||||
|
||||
local row = {
|
||||
type = ui.TYPE.Flex,
|
||||
props = {
|
||||
horizontal = true,
|
||||
},
|
||||
content = ui.content {
|
||||
name,
|
||||
{ props = { size = util.vector2(10, 0) } },
|
||||
recorder,
|
||||
},
|
||||
}
|
||||
local column = {
|
||||
type = ui.TYPE.Flex,
|
||||
content = ui.content {
|
||||
row,
|
||||
description,
|
||||
},
|
||||
}
|
||||
|
||||
return column
|
||||
end)
|
||||
|
||||
return {
|
||||
engineHandlers = {
|
||||
onKeyPress = function(key)
|
||||
if recording == nil then return end
|
||||
local binding = {
|
||||
code = key.code,
|
||||
type = recording.arg.type,
|
||||
key = recording.arg.key,
|
||||
}
|
||||
if key.code == input.KEY.Escape then -- TODO: prevent settings modal from closing
|
||||
binding.code = nil
|
||||
end
|
||||
bindingSection:set(recording.id, binding)
|
||||
local refresh = recording.refresh
|
||||
recording = nil
|
||||
refresh()
|
||||
end,
|
||||
}
|
||||
}
|
@ -6,8 +6,6 @@ local argumentSectionPostfix = 'Arguments'
|
||||
|
||||
local contextSection = storage.playerSection or storage.globalSection
|
||||
local groupSection = contextSection(groupSectionKey)
|
||||
groupSection:reset()
|
||||
groupSection:removeOnExit()
|
||||
|
||||
local function validateSettingOptions(options)
|
||||
if type(options) ~= 'table' then
|
||||
@ -92,7 +90,6 @@ local function registerGroup(options)
|
||||
}
|
||||
local valueSection = contextSection(options.key)
|
||||
local argumentSection = contextSection(options.key .. argumentSectionPostfix)
|
||||
argumentSection:removeOnExit()
|
||||
for i, opt in ipairs(options.settings) do
|
||||
local setting = registerSetting(opt)
|
||||
setting.order = i
|
||||
@ -120,6 +117,7 @@ return {
|
||||
argumentSection:set(settingKey, argument)
|
||||
end,
|
||||
setGlobalEvent = 'OMWSettingsGlobalSet',
|
||||
registerPageEvent = 'OmWSettingsRegisterPage',
|
||||
groupSectionKey = groupSectionKey,
|
||||
onLoad = function(saved)
|
||||
if not saved then return end
|
||||
|
@ -1,10 +1,12 @@
|
||||
local storage = require('openmw.storage')
|
||||
|
||||
local common = require('scripts.omw.settings.common')
|
||||
common.getSection(true, common.groupSectionKey):removeOnExit()
|
||||
|
||||
return {
|
||||
interfaceName = 'Settings',
|
||||
interface = {
|
||||
version = 1,
|
||||
registerGroup = common.registerGroup,
|
||||
updateRendererArgument = common.updateRendererArgument,
|
||||
},
|
||||
@ -17,4 +19,4 @@ return {
|
||||
storage.globalSection(e.groupKey):set(e.settingKey, e.value)
|
||||
end,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
local menu = require('openmw.menu')
|
||||
local ui = require('openmw.ui')
|
||||
local util = require('openmw.util')
|
||||
local async = require('openmw.async')
|
||||
@ -6,11 +7,14 @@ local storage = require('openmw.storage')
|
||||
local I = require('openmw.interfaces')
|
||||
|
||||
local common = require('scripts.omw.settings.common')
|
||||
-- :reset on startup instead of :removeOnExit
|
||||
common.getSection(false, common.groupSectionKey):reset()
|
||||
|
||||
local renderers = {}
|
||||
local function registerRenderer(name, renderFunction)
|
||||
renderers[name] = renderFunction
|
||||
end
|
||||
require('scripts.omw.settings.renderers')(registerRenderer)
|
||||
|
||||
local interfaceL10n = core.l10n('Interface')
|
||||
|
||||
@ -25,12 +29,12 @@ local growingIntreval = {
|
||||
grow = 1,
|
||||
},
|
||||
}
|
||||
local spacer = {
|
||||
local spacer = {
|
||||
props = {
|
||||
size = util.vector2(0, 10),
|
||||
},
|
||||
}
|
||||
local bigSpacer = {
|
||||
local bigSpacer = {
|
||||
props = {
|
||||
size = util.vector2(0, 50),
|
||||
},
|
||||
@ -45,7 +49,7 @@ local spacedLines = function(count)
|
||||
local content = {}
|
||||
table.insert(content, spacer)
|
||||
table.insert(content, stretchingLine)
|
||||
for i = 2, count do
|
||||
for _ = 2, count do
|
||||
table.insert(content, interval)
|
||||
table.insert(content, stretchingLine)
|
||||
end
|
||||
@ -267,11 +271,16 @@ end
|
||||
local function renderPage(page)
|
||||
local l10n = core.l10n(page.l10n)
|
||||
local sortedGroups = {}
|
||||
for i, v in ipairs(groups[page.key]) do sortedGroups[i] = v end
|
||||
for _, group in pairs(groups[page.key]) do
|
||||
table.insert(sortedGroups, group)
|
||||
end
|
||||
table.sort(sortedGroups, pageGroupComparator)
|
||||
local groupLayouts = {}
|
||||
for _, pageGroup in ipairs(sortedGroups) do
|
||||
local group = common.getSection(pageGroup.global, common.groupSectionKey):get(pageGroup.key)
|
||||
if not group then
|
||||
error(string.format('%s group "%s" was not found', pageGroup.global and 'Global' or 'Player', pageGroup.key))
|
||||
end
|
||||
table.insert(groupLayouts, renderGroup(group, pageGroup.global))
|
||||
end
|
||||
local groupsLayout = {
|
||||
@ -342,52 +351,99 @@ local function onSettingChanged(global)
|
||||
element:update()
|
||||
end)
|
||||
end
|
||||
|
||||
local function onGroupRegistered(global, key)
|
||||
local group = common.getSection(global, common.groupSectionKey):get(key)
|
||||
if not group then return end
|
||||
|
||||
groups[group.page] = groups[group.page] or {}
|
||||
local pageGroup = {
|
||||
local pageGroup = {
|
||||
key = group.key,
|
||||
global = global,
|
||||
order = group.order,
|
||||
}
|
||||
table.insert(groups[group.page], pageGroup)
|
||||
common.getSection(global, group.key):subscribe(onSettingChanged(global))
|
||||
common.getArgumentSection(global, group.key):subscribe(async:callback(function(_, settingKey)
|
||||
local groupKey = group.key
|
||||
local group = common.getSection(global, common.groupSectionKey):get(groupKey)
|
||||
if not group or not pageOptions[group.page] then return end
|
||||
|
||||
local value = common.getSection(global, group.key):get(settingKey)
|
||||
if not groups[group.page][pageGroup.key] then
|
||||
common.getSection(global, group.key):subscribe(onSettingChanged(global))
|
||||
common.getArgumentSection(global, group.key):subscribe(async:callback(function(_, settingKey)
|
||||
if settingKey == nil then return end
|
||||
|
||||
local element = pageOptions[group.page].element
|
||||
local groupsLayout = element.layout.content.groups
|
||||
local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)]
|
||||
local settingsContent = groupLayout.content.settings.content
|
||||
settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global)
|
||||
element:update()
|
||||
end))
|
||||
local group = common.getSection(global, common.groupSectionKey):get(group.key)
|
||||
if not group or not pageOptions[group.page] then return end
|
||||
|
||||
local value = common.getSection(global, group.key):get(settingKey)
|
||||
|
||||
local element = pageOptions[group.page].element
|
||||
local groupsLayout = element.layout.content.groups
|
||||
local groupLayout = groupsLayout.content[groupLayoutName(group.key, global)]
|
||||
local settingsContent = groupLayout.content.settings.content
|
||||
settingsContent[settingKey] = renderSetting(group, group.settings[settingKey], value, global)
|
||||
element:update()
|
||||
end))
|
||||
end
|
||||
|
||||
groups[group.page][pageGroup.key] = pageGroup
|
||||
|
||||
if not pages[group.page] then return end
|
||||
local options = renderPage(pages[group.page])
|
||||
if pageOptions[group.page] then
|
||||
pageOptions[group.page].element:destroy()
|
||||
else
|
||||
pageOptions[group.page] = {}
|
||||
end
|
||||
for k, v in pairs(options) do
|
||||
local renderedOptions = renderPage(pages[group.page])
|
||||
for k, v in pairs(renderedOptions) do
|
||||
pageOptions[group.page][k] = v
|
||||
end
|
||||
end
|
||||
local globalGroups = storage.globalSection(common.groupSectionKey)
|
||||
for groupKey in pairs(globalGroups:asTable()) do
|
||||
onGroupRegistered(true, groupKey)
|
||||
|
||||
local function updateGroups(global)
|
||||
local groupSection = common.getSection(global, common.groupSectionKey)
|
||||
for groupKey in pairs(groupSection:asTable()) do
|
||||
onGroupRegistered(global, groupKey)
|
||||
end
|
||||
groupSection:subscribe(async:callback(function(_, key)
|
||||
if key then
|
||||
onGroupRegistered(global, key)
|
||||
else
|
||||
for groupKey in pairs(groupSection:asTable()) do
|
||||
onGroupRegistered(global, groupKey)
|
||||
end
|
||||
end
|
||||
end))
|
||||
end
|
||||
|
||||
local updatePlayerGroups = function() updateGroups(false) end
|
||||
updatePlayerGroups()
|
||||
|
||||
local updateGlobalGroups = function() updateGroups(true) end
|
||||
|
||||
local menuGroups = {}
|
||||
local menuPages = {}
|
||||
|
||||
local function resetPlayerGroups()
|
||||
print('MENU reset player groups')
|
||||
local playerGroupsSection = storage.playerSection(common.groupSectionKey)
|
||||
for pageKey, page in pairs(groups) do
|
||||
for groupKey, group in pairs(page) do
|
||||
if not menuGroups[groupKey] and not group.global then
|
||||
page[groupKey] = nil
|
||||
playerGroupsSection:set(groupKey, nil)
|
||||
end
|
||||
end
|
||||
if pageOptions[pageKey] then
|
||||
pageOptions[pageKey].element:destroy()
|
||||
if not menuPages[pageKey] then
|
||||
ui.removeSettingsPage(pageOptions[pageKey])
|
||||
pageOptions[pageKey] = nil
|
||||
else
|
||||
local renderedOptions = renderPage(pages[pageKey])
|
||||
for k, v in pairs(renderedOptions) do
|
||||
pageOptions[pageKey][k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
globalGroups:subscribe(async:callback(function(_, key)
|
||||
if key then onGroupRegistered(true, key) end
|
||||
end))
|
||||
storage.playerSection(common.groupSectionKey):subscribe(async:callback(function(_, key)
|
||||
if key then onGroupRegistered(false, key) end
|
||||
end))
|
||||
|
||||
local function registerPage(options)
|
||||
if type(options) ~= 'table' then
|
||||
@ -413,11 +469,48 @@ local function registerPage(options)
|
||||
}
|
||||
pages[page.key] = page
|
||||
groups[page.key] = groups[page.key] or {}
|
||||
pageOptions[page.key] = renderPage(page)
|
||||
if pageOptions[page.key] then
|
||||
pageOptions[page.key].element:destroy()
|
||||
end
|
||||
pageOptions[page.key] = pageOptions[page.key] or {}
|
||||
local renderedOptions = renderPage(page)
|
||||
for k, v in pairs(renderedOptions) do
|
||||
pageOptions[page.key][k] = v
|
||||
end
|
||||
ui.registerSettingsPage(pageOptions[page.key])
|
||||
end
|
||||
|
||||
return {
|
||||
registerPage = registerPage,
|
||||
registerRenderer = registerRenderer,
|
||||
}
|
||||
interfaceName = 'Settings',
|
||||
interface = {
|
||||
version = 1,
|
||||
registerPage = function(options)
|
||||
registerPage(options)
|
||||
menuPages[options.key] = true
|
||||
end,
|
||||
registerRenderer = registerRenderer,
|
||||
registerGroup = function(options)
|
||||
if not options.permanentStorage then
|
||||
error('Menu scripts are only allowed to register setting groups with permanentStorage = true')
|
||||
end
|
||||
common.registerGroup(options)
|
||||
menuGroups[options.key] = true
|
||||
end,
|
||||
updateRendererArgument = common.updateRendererArgument,
|
||||
},
|
||||
engineHandlers = {
|
||||
onStateChanged = function()
|
||||
if menu.getState() == menu.STATE.Running then
|
||||
updatePlayerGroups()
|
||||
updateGlobalGroups()
|
||||
else
|
||||
resetPlayerGroups()
|
||||
end
|
||||
end,
|
||||
},
|
||||
eventHandlers = {
|
||||
[common.registerPageEvent] = function(options)
|
||||
registerPage(options)
|
||||
end,
|
||||
}
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
local common = require('scripts.omw.settings.common')
|
||||
local render = require('scripts.omw.settings.render')
|
||||
local types = require('openmw.types')
|
||||
local self = require('openmw.self')
|
||||
|
||||
require('scripts.omw.settings.renderers')(render.registerRenderer)
|
||||
local common = require('scripts.omw.settings.common')
|
||||
|
||||
local function registerPage(options)
|
||||
types.Player.sendMenuEvent(self, common.registerPageEvent, options)
|
||||
end
|
||||
|
||||
---
|
||||
-- @type PageOptions
|
||||
@ -71,11 +75,11 @@ return {
|
||||
-- local globalSettings = storage.globalSection('SettingsGlobalMyMod')
|
||||
interface = {
|
||||
---
|
||||
-- @field [parent=#Settings] #string version
|
||||
version = 0,
|
||||
-- @field [parent=#Settings] #number version
|
||||
version = 1,
|
||||
---
|
||||
-- @function [parent=#Settings] registerPage Register a page to be displayed in the settings menu,
|
||||
-- only available in player scripts
|
||||
-- available in player and menu scripts
|
||||
-- @param #PageOptions options
|
||||
-- @usage
|
||||
-- I.Settings.registerPage({
|
||||
@ -84,10 +88,10 @@ return {
|
||||
-- name = 'MyModName',
|
||||
-- description = 'MyModDescription',
|
||||
-- })---
|
||||
registerPage = render.registerPage,
|
||||
registerPage = registerPage,
|
||||
---
|
||||
-- @function [parent=#Settings] registerRenderer Register a renderer,
|
||||
-- only avaialable in player scripts
|
||||
-- only available in menu scripts (DEPRECATED in player scripts)
|
||||
-- @param #string key
|
||||
-- @param #function renderer A renderer function, receives setting's value,
|
||||
-- a function to change it and an argument from the setting options
|
||||
@ -107,10 +111,14 @@ return {
|
||||
-- },
|
||||
-- }
|
||||
-- end)
|
||||
registerRenderer = render.registerRenderer,
|
||||
registerRenderer = function(name)
|
||||
print(([[Can't register setting renderer "%s". registerRenderer and moved to Menu context Settings interface]])
|
||||
:format(name))
|
||||
end,
|
||||
---
|
||||
-- @function [parent=#Settings] registerGroup Register a group to be attached to a page,
|
||||
-- available both in player and global scripts
|
||||
-- available in player, menu and global scripts
|
||||
-- Note: menu scripts only allow group with permanentStorage = true, but can render the page before a game is loaded!
|
||||
-- @param #GroupOptions options
|
||||
-- @usage
|
||||
-- I.Settings.registerGroup {
|
||||
@ -140,7 +148,7 @@ return {
|
||||
registerGroup = common.registerGroup,
|
||||
---
|
||||
-- @function [parent=#Settings] updateRendererArgument Change the renderer argument of a setting
|
||||
-- available both in player and global scripts
|
||||
-- available both in player, menu and global scripts
|
||||
-- @param #string groupKey A settings group key
|
||||
-- @param #string settingKey A setting key
|
||||
-- @param argument A renderer argument
|
||||
|
@ -21,6 +21,7 @@ set(LUA_API_FILES
|
||||
openmw/util.lua
|
||||
openmw/vfs.lua
|
||||
openmw/world.lua
|
||||
openmw/menu.lua
|
||||
)
|
||||
|
||||
foreach (f ${LUA_API_FILES})
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
-- `openmw.ambient` controls background sounds, specific to given player (2D-sounds).
|
||||
-- Can be used only by local scripts, that are attached to a player.
|
||||
-- Can be used only by menu scripts and local scripts, that are attached to a player.
|
||||
-- @module ambient
|
||||
-- @usage local ambient = require('openmw.ambient')
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
-- `openmw.core` defines functions and types that are available in both local
|
||||
-- and global scripts.
|
||||
-- `openmw.core` defines functions and types that are available in local,
|
||||
-- global and menu scripts.
|
||||
-- @module core
|
||||
-- @usage local core = require('openmw.core')
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
-- `openmw.input` can be used only in scripts attached to a player.
|
||||
-- `openmw.input` can be used only in menu scripts and scripts attached to a player.
|
||||
-- @module input
|
||||
-- @usage local input = require('openmw.input')
|
||||
|
||||
|
72
files/lua_api/openmw/menu.lua
Normal file
72
files/lua_api/openmw/menu.lua
Normal file
@ -0,0 +1,72 @@
|
||||
---
|
||||
-- `openmw.menu` can be used only in menu scripts.
|
||||
-- @module menu
|
||||
-- @usage local menu = require('openmw.menu')
|
||||
|
||||
---
|
||||
-- @type STATE
|
||||
-- @field [parent=#STATE] NoGame
|
||||
-- @field [parent=#STATE] Running
|
||||
-- @field [parent=#STATE] Ended
|
||||
|
||||
---
|
||||
-- All possible game states returned by @{#menu.getState}
|
||||
-- @field [parent=#menu] #STATE STATE
|
||||
|
||||
---
|
||||
-- Current game state
|
||||
-- @function [parent=#menu] getState
|
||||
-- @return #STATE
|
||||
|
||||
---
|
||||
-- Start a new game
|
||||
-- @function [parent=#menu] newGame
|
||||
|
||||
---
|
||||
-- Load the game from a save slot
|
||||
-- @function [parent=#menu] loadGame
|
||||
-- @param #string directory name of the save directory (e. g. character)
|
||||
-- @param #string slotName name of the save slot
|
||||
|
||||
---
|
||||
-- Delete a saved game
|
||||
-- @function [parent=#menu] deleteGame
|
||||
-- @param #string directory name of the save directory (e. g. character)
|
||||
-- @param #string slotName name of the save slot
|
||||
|
||||
---
|
||||
-- Current save directory
|
||||
-- @function [parent=#menu] getCurrentSaveDir
|
||||
-- @return #string
|
||||
|
||||
---
|
||||
-- Save the game
|
||||
-- @function [parent=#menu] saveGame
|
||||
-- @param #string description human readable description of the save
|
||||
-- @param #string slotName name of the save slot
|
||||
|
||||
---
|
||||
-- @type SaveInfo
|
||||
-- @field #string description
|
||||
-- @field #string playerName
|
||||
-- @field #string playerLevel
|
||||
-- @field #number timePlayed Gameplay time for this saved game. Note: available even with [time played](../modding/settings/saves.html#timeplayed) turned off
|
||||
-- @field #number creationTime Time at which the game was saved, as a timestamp in seconds. Can be passed as the second argument to `os.data`.
|
||||
-- @field #list<#string> contentFiles
|
||||
|
||||
---
|
||||
-- List of all saves for the given directory
|
||||
-- @function [parent=#menu] getSaves
|
||||
-- @param #string directory name of the save directory (e. g. character)
|
||||
-- @return #list<#SaveInfo>
|
||||
|
||||
---
|
||||
-- List of all available saves, grouped by directory
|
||||
-- @function [parent=#menu] getAllSaves
|
||||
-- @return #map<#string, #list<#SaveInfo>>
|
||||
|
||||
---
|
||||
-- Exit the game
|
||||
-- @function [parent=#menu] quit
|
||||
|
||||
return nil
|
@ -17,13 +17,14 @@
|
||||
|
||||
---
|
||||
-- Get a section of the global storage; can be used by any script, but only global scripts can change values.
|
||||
-- Menu scripts can only access it when a game is running.
|
||||
-- Creates the section if it doesn't exist.
|
||||
-- @function [parent=#storage] globalSection
|
||||
-- @param #string sectionName
|
||||
-- @return #StorageSection
|
||||
|
||||
---
|
||||
-- Get a section of the player storage; can be used by player scripts only.
|
||||
-- Get a section of the player storage; can only be used by player and menu scripts.
|
||||
-- Creates the section if it doesn't exist.
|
||||
-- @function [parent=#storage] playerSection
|
||||
-- @param #string sectionName
|
||||
@ -36,7 +37,7 @@
|
||||
-- @return #table
|
||||
|
||||
---
|
||||
-- Get all global sections as a table; can be used by player scripts only.
|
||||
-- Get all player sections as a table; can only be used by player and menu scripts.
|
||||
-- Note that adding/removing items to the returned table doesn't create or remove sections.
|
||||
-- @function [parent=#storage] allPlayerSections
|
||||
-- @return #table
|
||||
|
@ -1045,6 +1045,12 @@
|
||||
-- Values that can be used with getControlSwitch/setControlSwitch.
|
||||
-- @field [parent=#Player] #CONTROL_SWITCH CONTROL_SWITCH
|
||||
|
||||
---
|
||||
-- Send an event to menu scripts.
|
||||
-- @function [parent=#core] sendMenuEvent
|
||||
-- @param openmw.core#GameObject player
|
||||
-- @param #string eventName
|
||||
-- @param eventData
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- @{#Armor} functions
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
-- `openmw.ui` controls user interface.
|
||||
-- Can be used only by local scripts, that are attached to a player.
|
||||
-- Can be used only by menu scripts and local scripts, that are attached to a player.
|
||||
-- @module ui
|
||||
-- @usage
|
||||
-- local ui = require('openmw.ui')
|
||||
@ -93,6 +93,11 @@
|
||||
-- @function [parent=#ui] registerSettingsPage
|
||||
-- @param #SettingsPageOptions page
|
||||
|
||||
---
|
||||
-- Removes the settings page
|
||||
-- @function [parent=#ui] removeSettingsPage
|
||||
-- @param #SettingsPageOptions page must be the exact same table of options as the one passed to registerSettingsPage
|
||||
|
||||
---
|
||||
-- Table with settings page options, passed as an argument to ui.registerSettingsPage
|
||||
-- @type SettingsPageOptions
|
||||
|
Loading…
x
Reference in New Issue
Block a user