mirror of
https://github.com/OpenMW/openmw.git
synced 2025-03-27 12:40:25 +00:00
Merge branch 'lua_actions_electric_boogaloo' into 'master'
Lua actions take 3 See merge request OpenMW/openmw!2628
This commit is contained in:
commit
e9f3e5c6d1
25 changed files with 1490 additions and 201 deletions
|
@ -19,6 +19,7 @@ apps/openmw_test_suite/lua/test_serialization.cpp
|
||||||
apps/openmw_test_suite/lua/test_storage.cpp
|
apps/openmw_test_suite/lua/test_storage.cpp
|
||||||
apps/openmw_test_suite/lua/test_ui_content.cpp
|
apps/openmw_test_suite/lua/test_ui_content.cpp
|
||||||
apps/openmw_test_suite/lua/test_utilpackage.cpp
|
apps/openmw_test_suite/lua/test_utilpackage.cpp
|
||||||
|
apps/openmw_test_suite/lua/test_inputactions.cpp
|
||||||
apps/openmw_test_suite/misc/test_endianness.cpp
|
apps/openmw_test_suite/misc/test_endianness.cpp
|
||||||
apps/openmw_test_suite/misc/test_resourcehelpers.cpp
|
apps/openmw_test_suite/misc/test_resourcehelpers.cpp
|
||||||
apps/openmw_test_suite/misc/test_stringops.cpp
|
apps/openmw_test_suite/misc/test_stringops.cpp
|
||||||
|
|
|
@ -29,6 +29,14 @@ namespace ESM
|
||||||
struct LuaScripts;
|
struct LuaScripts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace LuaUtil
|
||||||
|
{
|
||||||
|
namespace InputAction
|
||||||
|
{
|
||||||
|
class Registry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace MWBase
|
namespace MWBase
|
||||||
{
|
{
|
||||||
// \brief LuaManager is the central interface through which the engine invokes lua scripts.
|
// \brief LuaManager is the central interface through which the engine invokes lua scripts.
|
||||||
|
|
|
@ -4,12 +4,14 @@
|
||||||
#include <SDL_gamecontroller.h>
|
#include <SDL_gamecontroller.h>
|
||||||
#include <SDL_mouse.h>
|
#include <SDL_mouse.h>
|
||||||
|
|
||||||
|
#include <components/lua/inputactions.hpp>
|
||||||
#include <components/lua/luastate.hpp>
|
#include <components/lua/luastate.hpp>
|
||||||
#include <components/sdlutil/events.hpp>
|
#include <components/sdlutil/events.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/inputmanager.hpp"
|
#include "../mwbase/inputmanager.hpp"
|
||||||
#include "../mwinput/actions.hpp"
|
#include "../mwinput/actions.hpp"
|
||||||
|
#include "luamanagerimp.hpp"
|
||||||
|
|
||||||
namespace sol
|
namespace sol
|
||||||
{
|
{
|
||||||
|
@ -17,6 +19,16 @@ namespace sol
|
||||||
struct is_automagical<SDL_Keysym> : std::false_type
|
struct is_automagical<SDL_Keysym> : std::false_type
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct is_automagical<LuaUtil::InputAction::Info> : std::false_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct is_automagical<LuaUtil::InputAction::Registry> : std::false_type
|
||||||
|
{
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MWLua
|
namespace MWLua
|
||||||
|
@ -46,9 +58,121 @@ namespace MWLua
|
||||||
touchpadEvent["pressure"]
|
touchpadEvent["pressure"]
|
||||||
= sol::readonly_property([](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; });
|
= sol::readonly_property([](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; });
|
||||||
|
|
||||||
|
auto inputActions = context.mLua->sol().new_usertype<LuaUtil::InputAction::Registry>("InputActions");
|
||||||
|
inputActions[sol::meta_function::index]
|
||||||
|
= [](LuaUtil::InputAction::Registry& registry, std::string_view key) { return registry[key]; };
|
||||||
|
{
|
||||||
|
auto pairs = [](LuaUtil::InputAction::Registry& registry) {
|
||||||
|
auto next = [](LuaUtil::InputAction::Registry& registry, std::string_view key)
|
||||||
|
-> sol::optional<std::tuple<std::string_view, LuaUtil::InputAction::Info>> {
|
||||||
|
std::optional<std::string_view> nextKey(registry.nextKey(key));
|
||||||
|
if (!nextKey.has_value())
|
||||||
|
return sol::nullopt;
|
||||||
|
else
|
||||||
|
return std::make_tuple(*nextKey, registry[*nextKey].value());
|
||||||
|
};
|
||||||
|
return std::make_tuple(next, registry, registry.firstKey());
|
||||||
|
};
|
||||||
|
inputActions[sol::meta_function::pairs] = pairs;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto actionInfo = context.mLua->sol().new_usertype<LuaUtil::InputAction::Info>("ActionInfo", "key",
|
||||||
|
sol::property([](const LuaUtil::InputAction::Info& info) { return info.mKey; }), "name",
|
||||||
|
sol::property([](const LuaUtil::InputAction::Info& info) { return info.mName; }), "description",
|
||||||
|
sol::property([](const LuaUtil::InputAction::Info& info) { return info.mDescription; }), "type",
|
||||||
|
sol::property([](const LuaUtil::InputAction::Info& info) { return info.mType; }), "l10n",
|
||||||
|
sol::property([](const LuaUtil::InputAction::Info& info) { return info.mL10n; }), "defaultValue",
|
||||||
|
sol::property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; }));
|
||||||
|
|
||||||
|
auto inputTriggers = context.mLua->sol().new_usertype<LuaUtil::InputTrigger::Registry>("InputTriggers");
|
||||||
|
inputTriggers[sol::meta_function::index]
|
||||||
|
= [](LuaUtil::InputTrigger::Registry& registry, std::string_view key) { return registry[key]; };
|
||||||
|
{
|
||||||
|
auto pairs = [](LuaUtil::InputTrigger::Registry& registry) {
|
||||||
|
auto next = [](LuaUtil::InputTrigger::Registry& registry, std::string_view key)
|
||||||
|
-> sol::optional<std::tuple<std::string_view, LuaUtil::InputTrigger::Info>> {
|
||||||
|
std::optional<std::string_view> nextKey(registry.nextKey(key));
|
||||||
|
if (!nextKey.has_value())
|
||||||
|
return sol::nullopt;
|
||||||
|
else
|
||||||
|
return std::make_tuple(*nextKey, registry[*nextKey].value());
|
||||||
|
};
|
||||||
|
return std::make_tuple(next, registry, registry.firstKey());
|
||||||
|
};
|
||||||
|
inputTriggers[sol::meta_function::pairs] = pairs;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto triggerInfo = context.mLua->sol().new_usertype<LuaUtil::InputTrigger::Info>("TriggerInfo", "key",
|
||||||
|
sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mKey; }), "name",
|
||||||
|
sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mName; }), "description",
|
||||||
|
sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mDescription; }), "l10n",
|
||||||
|
sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mL10n; }));
|
||||||
|
|
||||||
MWBase::InputManager* input = MWBase::Environment::get().getInputManager();
|
MWBase::InputManager* input = MWBase::Environment::get().getInputManager();
|
||||||
sol::table api(context.mLua->sol(), sol::create);
|
sol::table api(context.mLua->sol(), sol::create);
|
||||||
|
|
||||||
|
api["ACTION_TYPE"]
|
||||||
|
= LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs<std::string_view, LuaUtil::InputAction::Type>({
|
||||||
|
{ "Boolean", LuaUtil::InputAction::Type::Boolean },
|
||||||
|
{ "Number", LuaUtil::InputAction::Type::Number },
|
||||||
|
{ "Range", LuaUtil::InputAction::Type::Range },
|
||||||
|
}));
|
||||||
|
|
||||||
|
api["actions"] = std::ref(context.mLuaManager->inputActions());
|
||||||
|
api["registerAction"] = [manager = context.mLuaManager](sol::table options) {
|
||||||
|
LuaUtil::InputAction::Info parsedOptions;
|
||||||
|
parsedOptions.mKey = options["key"].get<std::string_view>();
|
||||||
|
parsedOptions.mType = options["type"].get<LuaUtil::InputAction::Type>();
|
||||||
|
parsedOptions.mL10n = options["l10n"].get<std::string_view>();
|
||||||
|
parsedOptions.mName = options["name"].get<std::string_view>();
|
||||||
|
parsedOptions.mDescription = options["description"].get<std::string_view>();
|
||||||
|
parsedOptions.mDefaultValue = options["defaultValue"].get<sol::main_object>();
|
||||||
|
manager->inputActions().insert(parsedOptions);
|
||||||
|
};
|
||||||
|
api["bindAction"] = [manager = context.mLuaManager](
|
||||||
|
std::string_view key, const sol::table& callback, sol::table dependencies) {
|
||||||
|
std::vector<std::string_view> parsedDependencies;
|
||||||
|
parsedDependencies.reserve(dependencies.size());
|
||||||
|
for (size_t i = 1; i <= dependencies.size(); ++i)
|
||||||
|
{
|
||||||
|
sol::object dependency = dependencies[i];
|
||||||
|
if (!dependency.is<std::string_view>())
|
||||||
|
throw std::domain_error("The dependencies argument must be a list of Action keys");
|
||||||
|
parsedDependencies.push_back(dependency.as<std::string_view>());
|
||||||
|
}
|
||||||
|
if (!manager->inputActions().bind(key, LuaUtil::Callback::fromLua(callback), parsedDependencies))
|
||||||
|
throw std::domain_error("Cyclic action binding");
|
||||||
|
};
|
||||||
|
api["registerActionHandler"]
|
||||||
|
= [manager = context.mLuaManager](std::string_view key, const sol::table& callback) {
|
||||||
|
manager->inputActions().registerHandler(key, LuaUtil::Callback::fromLua(callback));
|
||||||
|
};
|
||||||
|
api["getBooleanActionValue"] = [manager = context.mLuaManager](std::string_view key) {
|
||||||
|
return manager->inputActions().valueOfType(key, LuaUtil::InputAction::Type::Boolean);
|
||||||
|
};
|
||||||
|
api["getNumberActionValue"] = [manager = context.mLuaManager](std::string_view key) {
|
||||||
|
return manager->inputActions().valueOfType(key, LuaUtil::InputAction::Type::Number);
|
||||||
|
};
|
||||||
|
api["getRangeActionValue"] = [manager = context.mLuaManager](std::string_view key) {
|
||||||
|
return manager->inputActions().valueOfType(key, LuaUtil::InputAction::Type::Range);
|
||||||
|
};
|
||||||
|
|
||||||
|
api["triggers"] = std::ref(context.mLuaManager->inputTriggers());
|
||||||
|
api["registerTrigger"] = [manager = context.mLuaManager](sol::table options) {
|
||||||
|
LuaUtil::InputTrigger::Info parsedOptions;
|
||||||
|
parsedOptions.mKey = options["key"].get<std::string_view>();
|
||||||
|
parsedOptions.mL10n = options["l10n"].get<std::string_view>();
|
||||||
|
parsedOptions.mName = options["name"].get<std::string_view>();
|
||||||
|
parsedOptions.mDescription = options["description"].get<std::string_view>();
|
||||||
|
manager->inputTriggers().insert(parsedOptions);
|
||||||
|
};
|
||||||
|
api["registerTriggerHandler"]
|
||||||
|
= [manager = context.mLuaManager](std::string_view key, const sol::table& callback) {
|
||||||
|
manager->inputTriggers().registerHandler(key, LuaUtil::Callback::fromLua(callback));
|
||||||
|
};
|
||||||
|
api["activateTrigger"]
|
||||||
|
= [manager = context.mLuaManager](std::string_view key) { manager->inputTriggers().activate(key); };
|
||||||
|
|
||||||
api["isIdle"] = [input]() { return input->isIdle(); };
|
api["isIdle"] = [input]() { return input->isIdle(); };
|
||||||
api["isActionPressed"] = [input](int action) { return input->actionIsActive(action); };
|
api["isActionPressed"] = [input](int action) { return input->actionIsActive(action); };
|
||||||
api["isKeyPressed"] = [](SDL_Scancode code) -> bool {
|
api["isKeyPressed"] = [](SDL_Scancode code) -> bool {
|
||||||
|
|
|
@ -225,10 +225,12 @@ namespace MWLua
|
||||||
playerScripts->processInputEvent(event);
|
playerScripts->processInputEvent(event);
|
||||||
}
|
}
|
||||||
mInputEvents.clear();
|
mInputEvents.clear();
|
||||||
|
double frameDuration = MWBase::Environment::get().getWorld()->getTimeManager()->isPaused()
|
||||||
|
? 0.0
|
||||||
|
: MWBase::Environment::get().getFrameDuration();
|
||||||
|
mInputActions.update(frameDuration);
|
||||||
if (playerScripts)
|
if (playerScripts)
|
||||||
playerScripts->onFrame(MWBase::Environment::get().getWorld()->getTimeManager()->isPaused()
|
playerScripts->onFrame(frameDuration);
|
||||||
? 0.0
|
|
||||||
: MWBase::Environment::get().getFrameDuration());
|
|
||||||
mProcessingInputEvents = false;
|
mProcessingInputEvents = false;
|
||||||
|
|
||||||
for (const std::string& message : mUIMessages)
|
for (const std::string& message : mUIMessages)
|
||||||
|
@ -291,6 +293,8 @@ namespace MWLua
|
||||||
}
|
}
|
||||||
mGlobalStorage.clearTemporaryAndRemoveCallbacks();
|
mGlobalStorage.clearTemporaryAndRemoveCallbacks();
|
||||||
mPlayerStorage.clearTemporaryAndRemoveCallbacks();
|
mPlayerStorage.clearTemporaryAndRemoveCallbacks();
|
||||||
|
mInputActions.clear();
|
||||||
|
mInputTriggers.clear();
|
||||||
for (int i = 0; i < 5; ++i)
|
for (int i = 0; i < 5; ++i)
|
||||||
lua_gc(mLua.sol(), LUA_GCCOLLECT, 0);
|
lua_gc(mLua.sol(), LUA_GCCOLLECT, 0);
|
||||||
}
|
}
|
||||||
|
@ -520,6 +524,8 @@ namespace MWLua
|
||||||
MWBase::Environment::get().getL10nManager()->dropCache();
|
MWBase::Environment::get().getL10nManager()->dropCache();
|
||||||
mUiResourceManager.clear();
|
mUiResourceManager.clear();
|
||||||
mLua.dropScriptCache();
|
mLua.dropScriptCache();
|
||||||
|
mInputActions.clear();
|
||||||
|
mInputTriggers.clear();
|
||||||
initConfiguration();
|
initConfiguration();
|
||||||
|
|
||||||
{ // Reload global scripts
|
{ // Reload global scripts
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <osg/Stats>
|
#include <osg/Stats>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
|
#include <components/lua/inputactions.hpp>
|
||||||
#include <components/lua/luastate.hpp>
|
#include <components/lua/luastate.hpp>
|
||||||
#include <components/lua/storage.hpp>
|
#include <components/lua/storage.hpp>
|
||||||
#include <components/lua_ui/resources.hpp>
|
#include <components/lua_ui/resources.hpp>
|
||||||
|
@ -144,6 +145,9 @@ namespace MWLua
|
||||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
||||||
std::string formatResourceUsageStats() const override;
|
std::string formatResourceUsageStats() const override;
|
||||||
|
|
||||||
|
LuaUtil::InputAction::Registry& inputActions() { return mInputActions; }
|
||||||
|
LuaUtil::InputTrigger::Registry& inputTriggers() { return mInputTriggers; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initConfiguration();
|
void initConfiguration();
|
||||||
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr,
|
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr,
|
||||||
|
@ -206,6 +210,9 @@ namespace MWLua
|
||||||
|
|
||||||
LuaUtil::LuaStorage mGlobalStorage{ mLua.sol() };
|
LuaUtil::LuaStorage mGlobalStorage{ mLua.sol() };
|
||||||
LuaUtil::LuaStorage mPlayerStorage{ mLua.sol() };
|
LuaUtil::LuaStorage mPlayerStorage{ mLua.sol() };
|
||||||
|
|
||||||
|
LuaUtil::InputAction::Registry mInputActions;
|
||||||
|
LuaUtil::InputTrigger::Registry mInputTriggers;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ file(GLOB UNITTEST_SRC_FILES
|
||||||
lua/test_l10n.cpp
|
lua/test_l10n.cpp
|
||||||
lua/test_storage.cpp
|
lua/test_storage.cpp
|
||||||
lua/test_async.cpp
|
lua/test_async.cpp
|
||||||
|
lua/test_inputactions.cpp
|
||||||
|
|
||||||
lua/test_ui_content.cpp
|
lua/test_ui_content.cpp
|
||||||
|
|
||||||
|
|
65
apps/openmw_test_suite/lua/test_inputactions.cpp
Normal file
65
apps/openmw_test_suite/lua/test_inputactions.cpp
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
#include "gmock/gmock.h"
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <components/lua/inputactions.hpp>
|
||||||
|
#include <components/lua/scriptscontainer.hpp>
|
||||||
|
|
||||||
|
#include "../testing_util.hpp"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
using namespace testing;
|
||||||
|
using namespace TestingOpenMW;
|
||||||
|
|
||||||
|
TEST(LuaInputActionsTest, MultiTree)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
LuaUtil::InputAction::MultiTree tree;
|
||||||
|
auto a = tree.insert();
|
||||||
|
auto b = tree.insert();
|
||||||
|
auto c = tree.insert();
|
||||||
|
auto d = tree.insert();
|
||||||
|
EXPECT_TRUE(tree.multiEdge(c, { a, b }));
|
||||||
|
EXPECT_TRUE(tree.multiEdge(a, { d }));
|
||||||
|
EXPECT_FALSE(tree.multiEdge(d, { c }));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
LuaUtil::InputAction::MultiTree tree;
|
||||||
|
auto a = tree.insert();
|
||||||
|
auto b = tree.insert();
|
||||||
|
auto c = tree.insert();
|
||||||
|
EXPECT_TRUE(tree.multiEdge(b, { a }));
|
||||||
|
EXPECT_TRUE(tree.multiEdge(c, { a, b }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LuaInputActionsTest, Registry)
|
||||||
|
{
|
||||||
|
sol::state lua;
|
||||||
|
LuaUtil::InputAction::Registry registry;
|
||||||
|
LuaUtil::InputAction::Info a({ "a", LuaUtil::InputAction::Type::Boolean, "test", "a_name", "a_description",
|
||||||
|
sol::make_object(lua, false) });
|
||||||
|
registry.insert(a);
|
||||||
|
LuaUtil::InputAction::Info b({ "b", LuaUtil::InputAction::Type::Boolean, "test", "b_name", "b_description",
|
||||||
|
sol::make_object(lua, false) });
|
||||||
|
registry.insert(b);
|
||||||
|
LuaUtil::Callback bindA({ lua.load("return function() return true end")(), sol::table(lua, sol::create) });
|
||||||
|
LuaUtil::Callback bindBToA(
|
||||||
|
{ lua.load("return function(_, _, aValue) return aValue end")(), sol::table(lua, sol::create) });
|
||||||
|
EXPECT_TRUE(registry.bind("a", bindA, {}));
|
||||||
|
EXPECT_TRUE(registry.bind("b", bindBToA, { "a" }));
|
||||||
|
registry.update(1.0);
|
||||||
|
sol::object bValue = registry.valueOfType("b", LuaUtil::InputAction::Type::Boolean);
|
||||||
|
EXPECT_TRUE(bValue.is<bool>());
|
||||||
|
LuaUtil::Callback badA(
|
||||||
|
{ lua.load("return function() return 'not_a_bool' end")(), sol::table(lua, sol::create) });
|
||||||
|
EXPECT_TRUE(registry.bind("a", badA, {}));
|
||||||
|
testing::internal::CaptureStderr();
|
||||||
|
registry.update(1.0);
|
||||||
|
sol::object aValue = registry.valueOfType("a", LuaUtil::InputAction::Type::Boolean);
|
||||||
|
EXPECT_TRUE(aValue.is<bool>());
|
||||||
|
bValue = registry.valueOfType("b", LuaUtil::InputAction::Type::Boolean);
|
||||||
|
EXPECT_TRUE(bValue.is<bool>() && bValue.as<bool>() == aValue.as<bool>());
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,7 +44,7 @@ list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}")
|
||||||
|
|
||||||
add_component_dir (lua
|
add_component_dir (lua
|
||||||
luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8
|
luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8
|
||||||
shapes/box
|
shapes/box inputactions
|
||||||
)
|
)
|
||||||
|
|
||||||
add_component_dir (l10n
|
add_component_dir (l10n
|
||||||
|
|
288
components/lua/inputactions.cpp
Normal file
288
components/lua/inputactions.cpp
Normal file
|
@ -0,0 +1,288 @@
|
||||||
|
#include "inputactions.hpp"
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
#include <components/misc/strings/format.hpp>
|
||||||
|
|
||||||
|
#include "luastate.hpp"
|
||||||
|
|
||||||
|
namespace LuaUtil
|
||||||
|
{
|
||||||
|
namespace InputAction
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::string_view typeName(Type actionType)
|
||||||
|
{
|
||||||
|
switch (actionType)
|
||||||
|
{
|
||||||
|
case Type::Boolean:
|
||||||
|
return "Boolean";
|
||||||
|
case Type::Number:
|
||||||
|
return "Number";
|
||||||
|
case Type::Range:
|
||||||
|
return "Range";
|
||||||
|
default:
|
||||||
|
throw std::logic_error("Unknown input action type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiTree::Node MultiTree::insert()
|
||||||
|
{
|
||||||
|
size_t nextId = size();
|
||||||
|
mChildren.push_back({});
|
||||||
|
mParents.push_back({});
|
||||||
|
return nextId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MultiTree::validateTree() const
|
||||||
|
{
|
||||||
|
std::vector<bool> complete(size(), false);
|
||||||
|
traverse([&complete](Node node) { complete[node] = true; });
|
||||||
|
return std::find(complete.begin(), complete.end(), false) == complete.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Function>
|
||||||
|
void MultiTree::traverse(Function callback) const
|
||||||
|
{
|
||||||
|
std::queue<Node> nodeQueue;
|
||||||
|
std::vector<bool> complete(size(), false);
|
||||||
|
for (Node root = 0; root < size(); ++root)
|
||||||
|
{
|
||||||
|
if (!complete[root])
|
||||||
|
nodeQueue.push(root);
|
||||||
|
while (!nodeQueue.empty())
|
||||||
|
{
|
||||||
|
Node node = nodeQueue.back();
|
||||||
|
nodeQueue.pop();
|
||||||
|
|
||||||
|
bool isComplete = true;
|
||||||
|
for (Node parent : mParents[node])
|
||||||
|
isComplete = isComplete && complete[parent];
|
||||||
|
complete[node] = isComplete;
|
||||||
|
if (isComplete)
|
||||||
|
{
|
||||||
|
callback(node);
|
||||||
|
for (Node child : mChildren[node])
|
||||||
|
nodeQueue.push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MultiTree::multiEdge(Node target, const std::vector<Node>& source)
|
||||||
|
{
|
||||||
|
mParents[target].reserve(mParents[target].size() + source.size());
|
||||||
|
for (Node s : source)
|
||||||
|
{
|
||||||
|
mParents[target].push_back(s);
|
||||||
|
mChildren[s].push_back(target);
|
||||||
|
}
|
||||||
|
bool validTree = validateTree();
|
||||||
|
if (!validTree)
|
||||||
|
{
|
||||||
|
for (Node s : source)
|
||||||
|
{
|
||||||
|
mParents[target].pop_back();
|
||||||
|
mChildren[s].pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return validTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool validateActionValue(sol::object value, Type type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case Type::Boolean:
|
||||||
|
return value.get_type() == sol::type::boolean;
|
||||||
|
case Type::Number:
|
||||||
|
return value.get_type() == sol::type::number;
|
||||||
|
case Type::Range:
|
||||||
|
if (value.get_type() != sol::type::number)
|
||||||
|
return false;
|
||||||
|
double d = value.as<double>();
|
||||||
|
return 0.0 <= d && d <= 1.0;
|
||||||
|
}
|
||||||
|
throw std::invalid_argument("Unknown action type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Registry::insert(Info info)
|
||||||
|
{
|
||||||
|
if (mIds.find(info.mKey) != mIds.end())
|
||||||
|
throw std::domain_error(Misc::StringUtils::format("Action key \"%s\" is already in use", info.mKey));
|
||||||
|
if (info.mKey.empty())
|
||||||
|
throw std::domain_error("Action key can't be an empty string");
|
||||||
|
if (info.mL10n.empty())
|
||||||
|
throw std::domain_error("Localization context can't be empty");
|
||||||
|
if (!validateActionValue(info.mDefaultValue, info.mType))
|
||||||
|
throw std::logic_error(Misc::StringUtils::format(
|
||||||
|
"Invalid value: \"%s\" for action \"%s\"", LuaUtil::toString(info.mDefaultValue), info.mKey));
|
||||||
|
Id id = mBindingTree.insert();
|
||||||
|
mKeys.push_back(info.mKey);
|
||||||
|
mIds[std::string(info.mKey)] = id;
|
||||||
|
mInfo.push_back(info);
|
||||||
|
mHandlers.push_back({});
|
||||||
|
mBindings.push_back({});
|
||||||
|
mValues.push_back(info.mDefaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> Registry::nextKey(std::string_view key) const
|
||||||
|
{
|
||||||
|
auto it = mIds.find(key);
|
||||||
|
if (it == mIds.end())
|
||||||
|
return std::nullopt;
|
||||||
|
auto nextId = it->second + 1;
|
||||||
|
if (nextId >= mKeys.size())
|
||||||
|
return std::nullopt;
|
||||||
|
return mKeys.at(nextId);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Info> Registry::operator[](std::string_view actionKey)
|
||||||
|
{
|
||||||
|
auto iter = mIds.find(actionKey);
|
||||||
|
if (iter == mIds.end())
|
||||||
|
return std::nullopt;
|
||||||
|
return mInfo[iter->second];
|
||||||
|
}
|
||||||
|
|
||||||
|
Registry::Id Registry::safeIdByKey(std::string_view key)
|
||||||
|
{
|
||||||
|
auto iter = mIds.find(key);
|
||||||
|
if (iter == mIds.end())
|
||||||
|
throw std::logic_error(Misc::StringUtils::format("Unknown action key: \"%s\"", key));
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Registry::bind(
|
||||||
|
std::string_view key, const LuaUtil::Callback& callback, const std::vector<std::string_view>& dependencies)
|
||||||
|
{
|
||||||
|
Id id = safeIdByKey(key);
|
||||||
|
std::vector<Id> dependencyIds;
|
||||||
|
dependencyIds.reserve(dependencies.size());
|
||||||
|
for (std::string_view s : dependencies)
|
||||||
|
dependencyIds.push_back(safeIdByKey(s));
|
||||||
|
bool validEdge = mBindingTree.multiEdge(id, dependencyIds);
|
||||||
|
if (validEdge)
|
||||||
|
mBindings[id].push_back(Binding{
|
||||||
|
callback,
|
||||||
|
std::move(dependencyIds),
|
||||||
|
});
|
||||||
|
return validEdge;
|
||||||
|
}
|
||||||
|
|
||||||
|
sol::object Registry::valueOfType(std::string_view key, Type type)
|
||||||
|
{
|
||||||
|
Id id = safeIdByKey(key);
|
||||||
|
Info info = mInfo[id];
|
||||||
|
if (info.mType != type)
|
||||||
|
throw std::logic_error(
|
||||||
|
Misc::StringUtils::format("Attempt to get value of type \"%s\" from action \"%s\" with type \"%s\"",
|
||||||
|
typeName(type), key, typeName(info.mType)));
|
||||||
|
return mValues[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Registry::update(double dt)
|
||||||
|
{
|
||||||
|
std::vector<sol::object> dependencyValues;
|
||||||
|
mBindingTree.traverse([this, &dependencyValues, dt](Id node) {
|
||||||
|
sol::main_object newValue = mValues[node];
|
||||||
|
std::vector<Binding>& bindings = mBindings[node];
|
||||||
|
bindings.erase(std::remove_if(bindings.begin(), bindings.end(),
|
||||||
|
[&](const Binding& binding) {
|
||||||
|
if (!binding.mCallback.isValid())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
dependencyValues.clear();
|
||||||
|
for (Id parent : binding.mDependencies)
|
||||||
|
dependencyValues.push_back(mValues[parent]);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
newValue = sol::main_object(
|
||||||
|
binding.mCallback.call(dt, newValue, sol::as_args(dependencyValues)));
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
if (!validateActionValue(newValue, mInfo[node].mType))
|
||||||
|
Log(Debug::Error) << Misc::StringUtils::format(
|
||||||
|
"Error due to invalid value of action \"%s\"(\"%s\"): ", mKeys[node],
|
||||||
|
LuaUtil::toString(newValue))
|
||||||
|
<< e.what();
|
||||||
|
else
|
||||||
|
Log(Debug::Error) << "Error in callback: " << e.what();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}),
|
||||||
|
bindings.end());
|
||||||
|
|
||||||
|
if (!validateActionValue(newValue, mInfo[node].mType))
|
||||||
|
Log(Debug::Error) << Misc::StringUtils::format(
|
||||||
|
"Invalid value of action \"%s\": %s", mKeys[node], LuaUtil::toString(newValue));
|
||||||
|
if (mValues[node] != newValue)
|
||||||
|
{
|
||||||
|
mValues[node] = sol::object(newValue);
|
||||||
|
std::vector<LuaUtil::Callback>& handlers = mHandlers[node];
|
||||||
|
handlers.erase(std::remove_if(handlers.begin(), handlers.end(),
|
||||||
|
[&](const LuaUtil::Callback& handler) {
|
||||||
|
if (!handler.isValid())
|
||||||
|
return true;
|
||||||
|
handler.tryCall(newValue);
|
||||||
|
return false;
|
||||||
|
}),
|
||||||
|
handlers.end());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace InputTrigger
|
||||||
|
{
|
||||||
|
Registry::Id Registry::safeIdByKey(std::string_view key)
|
||||||
|
{
|
||||||
|
auto it = mIds.find(key);
|
||||||
|
if (it == mIds.end())
|
||||||
|
throw std::domain_error(Misc::StringUtils::format("Unknown trigger key \"%s\"", key));
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Registry::insert(Info info)
|
||||||
|
{
|
||||||
|
if (mIds.find(info.mKey) != mIds.end())
|
||||||
|
throw std::domain_error(Misc::StringUtils::format("Trigger key \"%s\" is already in use", info.mKey));
|
||||||
|
if (info.mKey.empty())
|
||||||
|
throw std::domain_error("Trigger key can't be an empty string");
|
||||||
|
if (info.mL10n.empty())
|
||||||
|
throw std::domain_error("Localization context can't be empty");
|
||||||
|
Id id = mIds.size();
|
||||||
|
mIds[info.mKey] = id;
|
||||||
|
mInfo.push_back(info);
|
||||||
|
mHandlers.push_back({});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Registry::registerHandler(std::string_view key, const LuaUtil::Callback& callback)
|
||||||
|
{
|
||||||
|
Id id = safeIdByKey(key);
|
||||||
|
mHandlers[id].push_back(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Registry::activate(std::string_view key)
|
||||||
|
{
|
||||||
|
Id id = safeIdByKey(key);
|
||||||
|
std::vector<LuaUtil::Callback>& handlers = mHandlers[id];
|
||||||
|
handlers.erase(std::remove_if(handlers.begin(), handlers.end(),
|
||||||
|
[&](const LuaUtil::Callback& handler) {
|
||||||
|
if (!handler.isValid())
|
||||||
|
return true;
|
||||||
|
handler.tryCall();
|
||||||
|
return false;
|
||||||
|
}),
|
||||||
|
handlers.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
153
components/lua/inputactions.hpp
Normal file
153
components/lua/inputactions.hpp
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
#ifndef COMPONENTS_LUA_INPUTACTIONS
|
||||||
|
#define COMPONENTS_LUA_INPUTACTIONS
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <sol/sol.hpp>
|
||||||
|
|
||||||
|
#include <components/lua/asyncpackage.hpp>
|
||||||
|
#include <components/lua/scriptscontainer.hpp>
|
||||||
|
#include <components/misc/algorithm.hpp>
|
||||||
|
|
||||||
|
namespace LuaUtil::InputAction
|
||||||
|
{
|
||||||
|
enum class Type
|
||||||
|
{
|
||||||
|
Boolean,
|
||||||
|
Number,
|
||||||
|
Range,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Info
|
||||||
|
{
|
||||||
|
std::string mKey;
|
||||||
|
Type mType;
|
||||||
|
std::string mL10n;
|
||||||
|
std::string mName;
|
||||||
|
std::string mDescription;
|
||||||
|
sol::main_object mDefaultValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MultiTree
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Node = size_t;
|
||||||
|
|
||||||
|
Node insert();
|
||||||
|
bool multiEdge(Node target, const std::vector<Node>& source);
|
||||||
|
size_t size() const { return mParents.size(); }
|
||||||
|
|
||||||
|
template <typename Function> // Function = void(Node)
|
||||||
|
void traverse(Function callback) const;
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
mParents.clear();
|
||||||
|
mChildren.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::vector<Node>> mParents;
|
||||||
|
std::vector<std::vector<Node>> mChildren;
|
||||||
|
|
||||||
|
bool validateTree() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Registry
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using ConstIterator = std::vector<Info>::const_iterator;
|
||||||
|
void insert(Info info);
|
||||||
|
size_t size() const { return mKeys.size(); }
|
||||||
|
std::optional<std::string> firstKey() const { return mKeys.empty() ? std::nullopt : std::optional(mKeys[0]); }
|
||||||
|
std::optional<std::string> nextKey(std::string_view key) const;
|
||||||
|
std::optional<Info> operator[](std::string_view actionKey);
|
||||||
|
bool bind(
|
||||||
|
std::string_view key, const LuaUtil::Callback& callback, const std::vector<std::string_view>& dependencies);
|
||||||
|
sol::object valueOfType(std::string_view key, Type type);
|
||||||
|
void update(double dt);
|
||||||
|
void registerHandler(std::string_view key, const LuaUtil::Callback& handler)
|
||||||
|
{
|
||||||
|
mHandlers[safeIdByKey(key)].push_back(handler);
|
||||||
|
}
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
mKeys.clear();
|
||||||
|
mIds.clear();
|
||||||
|
mInfo.clear();
|
||||||
|
mHandlers.clear();
|
||||||
|
mBindings.clear();
|
||||||
|
mValues.clear();
|
||||||
|
mBindingTree.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
using Id = MultiTree::Node;
|
||||||
|
Id safeIdByKey(std::string_view key);
|
||||||
|
struct Binding
|
||||||
|
{
|
||||||
|
LuaUtil::Callback mCallback;
|
||||||
|
std::vector<Id> mDependencies;
|
||||||
|
};
|
||||||
|
std::vector<std::string> mKeys;
|
||||||
|
std::unordered_map<std::string, Id, Misc::StringUtils::StringHash, std::equal_to<>> mIds;
|
||||||
|
std::vector<Info> mInfo;
|
||||||
|
std::vector<std::vector<LuaUtil::Callback>> mHandlers;
|
||||||
|
std::vector<std::vector<Binding>> mBindings;
|
||||||
|
std::vector<sol::object> mValues;
|
||||||
|
MultiTree mBindingTree;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace LuaUtil::InputTrigger
|
||||||
|
{
|
||||||
|
struct Info
|
||||||
|
{
|
||||||
|
std::string mKey;
|
||||||
|
std::string mL10n;
|
||||||
|
std::string mName;
|
||||||
|
std::string mDescription;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Registry
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::optional<std::string> firstKey() const
|
||||||
|
{
|
||||||
|
return mIds.empty() ? std::nullopt : std::optional(mIds.begin()->first);
|
||||||
|
}
|
||||||
|
std::optional<std::string> nextKey(std::string_view key) const
|
||||||
|
{
|
||||||
|
auto it = mIds.find(key);
|
||||||
|
if (it == mIds.end() || ++it == mIds.end())
|
||||||
|
return std::nullopt;
|
||||||
|
return it->first;
|
||||||
|
}
|
||||||
|
std::optional<Info> operator[](std::string_view key)
|
||||||
|
{
|
||||||
|
Id id = safeIdByKey(key);
|
||||||
|
return mInfo[id];
|
||||||
|
}
|
||||||
|
void insert(Info info);
|
||||||
|
void registerHandler(std::string_view key, const LuaUtil::Callback& callback);
|
||||||
|
void activate(std::string_view key);
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
mInfo.clear();
|
||||||
|
mHandlers.clear();
|
||||||
|
mIds.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
using Id = size_t;
|
||||||
|
Id safeIdByKey(std::string_view key);
|
||||||
|
std::unordered_map<std::string, Id, Misc::StringUtils::StringHash, std::equal_to<>> mIds;
|
||||||
|
std::vector<Info> mInfo;
|
||||||
|
std::vector<std::vector<LuaUtil::Callback>> mHandlers;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // COMPONENTS_LUA_INPUTACTIONS
|
|
@ -99,6 +99,13 @@ namespace Misc::StringUtils
|
||||||
bool operator()(std::string_view left, std::string_view right) const { return ciLess(left, right); }
|
bool operator()(std::string_view left, std::string_view right) const { return ciLess(left, right); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct StringHash
|
||||||
|
{
|
||||||
|
using is_transparent = void;
|
||||||
|
[[nodiscard]] size_t operator()(std::string_view sv) const { return std::hash<std::string_view>{}(sv); }
|
||||||
|
[[nodiscard]] size_t operator()(const std::string& s) const { return std::hash<std::string>{}(s); }
|
||||||
|
};
|
||||||
|
|
||||||
/** @brief Replaces all occurrences of a string in another string.
|
/** @brief Replaces all occurrences of a string in another string.
|
||||||
*
|
*
|
||||||
* @param str The string to operate on.
|
* @param str The string to operate on.
|
||||||
|
|
|
@ -109,7 +109,8 @@ Engine handler is a function defined by a script, that can be called by the engi
|
||||||
| Usage example:
|
| Usage example:
|
||||||
| ``if id == input.CONTROLLER_BUTTON.LeftStick then ...``
|
| ``if id == input.CONTROLLER_BUTTON.LeftStick then ...``
|
||||||
* - onInputAction(id)
|
* - onInputAction(id)
|
||||||
- | `Game control <openmw_input.html##(ACTION)>`_ is pressed.
|
- | (DEPRECATED, use `registerActionHandler <openmw_input.html##(registerActionHandler)>`_)
|
||||||
|
| `Game control <openmw_input.html##(ACTION)>`_ is pressed.
|
||||||
| Usage example:
|
| Usage example:
|
||||||
| ``if id == input.ACTION.ToggleWeapon then ...``
|
| ``if id == input.ACTION.ToggleWeapon then ...``
|
||||||
* - onTouchPress(touchEvent)
|
* - onTouchPress(touchEvent)
|
||||||
|
|
|
@ -126,3 +126,27 @@ Table with the following optional fields:
|
||||||
* - disabled
|
* - disabled
|
||||||
- bool (false)
|
- bool (false)
|
||||||
- Disables changing the setting from the UI
|
- Disables changing the setting from the UI
|
||||||
|
|
||||||
|
inputBinding
|
||||||
|
-----
|
||||||
|
|
||||||
|
Allows the user to bind inputs to an action or trigger
|
||||||
|
|
||||||
|
**Argument**
|
||||||
|
|
||||||
|
Table with the following fields:
|
||||||
|
|
||||||
|
.. list-table::
|
||||||
|
:header-rows: 1
|
||||||
|
:widths: 20 20 60
|
||||||
|
|
||||||
|
* - name
|
||||||
|
- type (default)
|
||||||
|
- description
|
||||||
|
* - type
|
||||||
|
- 'keyboardPress', 'keyboardHold'
|
||||||
|
- The type of input that's allowed to be bound
|
||||||
|
* - key
|
||||||
|
- #string
|
||||||
|
- Key of the action or trigger to which the input is bound
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,8 @@ set(BUILTIN_DATA_FILES
|
||||||
scripts/omw/ui.lua
|
scripts/omw/ui.lua
|
||||||
scripts/omw/usehandlers.lua
|
scripts/omw/usehandlers.lua
|
||||||
scripts/omw/worldeventhandlers.lua
|
scripts/omw/worldeventhandlers.lua
|
||||||
|
scripts/omw/input/actionbindings.lua
|
||||||
|
scripts/omw/input/smoothmovement.lua
|
||||||
|
|
||||||
shaders/adjustments.omwfx
|
shaders/adjustments.omwfx
|
||||||
shaders/bloomlinear.omwfx
|
shaders/bloomlinear.omwfx
|
||||||
|
|
|
@ -13,6 +13,8 @@ GLOBAL: scripts/omw/worldeventhandlers.lua
|
||||||
PLAYER: scripts/omw/mechanics/playercontroller.lua
|
PLAYER: scripts/omw/mechanics/playercontroller.lua
|
||||||
PLAYER: scripts/omw/playercontrols.lua
|
PLAYER: scripts/omw/playercontrols.lua
|
||||||
PLAYER: scripts/omw/camera/camera.lua
|
PLAYER: scripts/omw/camera/camera.lua
|
||||||
|
PLAYER: scripts/omw/input/actionbindings.lua
|
||||||
|
PLAYER: scripts/omw/input/smoothmovement.lua
|
||||||
NPC,CREATURE: scripts/omw/ai.lua
|
NPC,CREATURE: scripts/omw/ai.lua
|
||||||
|
|
||||||
# User interface
|
# User interface
|
||||||
|
|
|
@ -10,7 +10,76 @@ alwaysRunDescription: |
|
||||||
|
|
||||||
toggleSneak: "Toggle sneak"
|
toggleSneak: "Toggle sneak"
|
||||||
toggleSneakDescription: |
|
toggleSneakDescription: |
|
||||||
This setting causes the behavior of the sneak key (bound to Ctrl by default)
|
This setting causes the sneak key (bound to Ctrl by default) to toggle sneaking on and off
|
||||||
to toggle sneaking on and off rather than requiring the key to be held down while sneaking.
|
rather than requiring the key to be held down while sneaking.
|
||||||
Players that spend significant time sneaking may find the character easier to control with this option enabled.
|
Players that spend significant time sneaking may find the character easier to control with this option enabled.
|
||||||
|
|
||||||
|
smoothControllerMovement: "Smooth controller movement"
|
||||||
|
smoothControllerMovementDescription: |
|
||||||
|
Enables smooth movement with controller stick, with no abrupt switch from walking to running.
|
||||||
|
|
||||||
|
TogglePOV_name: "Toggle POV"
|
||||||
|
TogglePOV_description: "Toggle between first and third person view. Hold to enter preview mode."
|
||||||
|
|
||||||
|
Zoom3rdPerson_name: "Zoom In/Out"
|
||||||
|
Zoom3rdPerson_description: "Moves the camera closer / further away when in third person view."
|
||||||
|
|
||||||
|
MoveForward_name: "Move Forward"
|
||||||
|
MoveForward_description: "Can cancel out with Move Backward"
|
||||||
|
|
||||||
|
MoveBackward_name: "Move Backward"
|
||||||
|
MoveBackward_description: "Can cancel out with Move Forward"
|
||||||
|
|
||||||
|
MoveLeft_name: "Move Left"
|
||||||
|
MoveLeft_description: "Can cancel out with Move Right"
|
||||||
|
|
||||||
|
MoveRight_name: "Move Right"
|
||||||
|
MoveRight_description: "Can cancel out with Move Left"
|
||||||
|
|
||||||
|
Use_name: "Use"
|
||||||
|
Use_description: "Attack with a weapon or cast a spell depending on current stance"
|
||||||
|
|
||||||
|
Run_name: "Run"
|
||||||
|
Run_description: "Hold to run/walk, depending on the Always Run setting"
|
||||||
|
|
||||||
|
AlwaysRun_name: "Always Run"
|
||||||
|
AlwaysRun_description: "Toggle the Always Run setting"
|
||||||
|
|
||||||
|
Jump_name: "Jump"
|
||||||
|
Jump_description: "Jump whenever you are on the ground"
|
||||||
|
|
||||||
|
AutoMove_name: "Auto Run"
|
||||||
|
AutoMove_description: "Toggle continous forward movement"
|
||||||
|
|
||||||
|
Sneak_name: "Sneak"
|
||||||
|
Sneak_description: "Hold to sneak, if the Toggle Sneak setting is off"
|
||||||
|
|
||||||
|
ToggleSneak_name: "Toggle Sneak"
|
||||||
|
ToggleSneak_description: "Toggle sneak, if the Toggle Sneak setting is on"
|
||||||
|
|
||||||
|
ToggleWeapon_name: "Ready Weapon"
|
||||||
|
ToggleWeapon_description: "Enter or leave the weapon stance"
|
||||||
|
|
||||||
|
ToggleSpell_name: "Ready Magic"
|
||||||
|
ToggleSpell_description: "Enter or leave the magic stance"
|
||||||
|
|
||||||
|
Inventory_name: "Inventory"
|
||||||
|
Inventory_description: "Open the inventory"
|
||||||
|
|
||||||
|
Journal_name: "Journal"
|
||||||
|
Journal_description: "Open the journal"
|
||||||
|
|
||||||
|
QuickKeysMenu_name: "QuickKeysMenu"
|
||||||
|
QuickKeysMenu_description: "Open the quick keys menu"
|
||||||
|
|
||||||
|
SmoothMoveForward_name: "Smooth Move Forward"
|
||||||
|
SmoothMoveForward_description: "Forward movement adjusted for smooth Walk-Run transitions"
|
||||||
|
|
||||||
|
SmoothMoveBackward_name: "Smooth Move Backward"
|
||||||
|
SmoothMoveBackward_description: "Backward movement adjusted for smooth Walk-Run transitions"
|
||||||
|
|
||||||
|
SmoothMoveLeft_name: "Smooth Move Left"
|
||||||
|
SmoothMoveLeft_description: "Left movement adjusted for smooth Walk-Run transitions"
|
||||||
|
|
||||||
|
SkmoothMoveRight_name: "SmoothMove Right"
|
||||||
|
SkmoothMoveRight_description: "Right movement adjusted for smooth Walk-Run transitions"
|
||||||
|
|
|
@ -14,3 +14,6 @@ toggleSneakDescription: |
|
||||||
Une simple pression de la touche associée (Ctrl par défaut) active le mode discrétion, une seconde pression désactive le mode discrétion.\n\n
|
Une simple pression de la touche associée (Ctrl par défaut) active le mode discrétion, une seconde pression désactive le mode discrétion.\n\n
|
||||||
Il n'est plus nécessaire de maintenir une touche appuyée pour que le mode discrétion soit actif.\n\n
|
Il n'est plus nécessaire de maintenir une touche appuyée pour que le mode discrétion soit actif.\n\n
|
||||||
Certains joueurs ayant une utilisation intensive du mode discrétion considèrent qu'il est plus aisé de contrôler leur personnage ainsi.
|
Certains joueurs ayant une utilisation intensive du mode discrétion considèrent qu'il est plus aisé de contrôler leur personnage ainsi.
|
||||||
|
|
||||||
|
# smoothControllerMovement
|
||||||
|
# smoothControllerMovementDescription
|
||||||
|
|
|
@ -14,3 +14,5 @@ toggleSneakDescription: |
|
||||||
чтобы красться, её достаточно нажать единожды для переключения положения, а не зажимать.
|
чтобы красться, её достаточно нажать единожды для переключения положения, а не зажимать.
|
||||||
Игрокам, которые много времени крадутся, может быть проще управлять персонажем, когда опция включена.
|
Игрокам, которые много времени крадутся, может быть проще управлять персонажем, когда опция включена.
|
||||||
|
|
||||||
|
# smoothControllerMovement
|
||||||
|
# smoothControllerMovementDescription
|
||||||
|
|
|
@ -13,3 +13,6 @@ toggleSneakDescription: |
|
||||||
Denna inställningen gör att smygknappen (förinställt till ctrl) slår smygning på eller av vid ett knapptryck
|
Denna inställningen gör att smygknappen (förinställt till ctrl) slår smygning på eller av vid ett knapptryck
|
||||||
istället för att att kräva att knappen hålls nedtryckt för att smyga.
|
istället för att att kräva att knappen hålls nedtryckt för att smyga.
|
||||||
Spelare som spenderar mycket tid med att smyga lär ha lättare att kontrollera rollfiguren med denna funktion aktiverad.
|
Spelare som spenderar mycket tid med att smyga lär ha lättare att kontrollera rollfiguren med denna funktion aktiverad.
|
||||||
|
|
||||||
|
# smoothControllerMovement
|
||||||
|
# smoothControllerMovementDescription
|
||||||
|
|
|
@ -10,6 +10,24 @@ local I = require('openmw.interfaces')
|
||||||
local Actor = require('openmw.types').Actor
|
local Actor = require('openmw.types').Actor
|
||||||
local Player = require('openmw.types').Player
|
local Player = require('openmw.types').Player
|
||||||
|
|
||||||
|
input.registerAction {
|
||||||
|
key = 'TogglePOV',
|
||||||
|
l10n = 'OMWControls',
|
||||||
|
name = 'TogglePOV_name',
|
||||||
|
description = 'TogglePOV_description',
|
||||||
|
type = input.ACTION_TYPE.Boolean,
|
||||||
|
defaultValue = false,
|
||||||
|
}
|
||||||
|
|
||||||
|
input.registerAction {
|
||||||
|
key = 'Zoom3rdPerson',
|
||||||
|
l10n = 'OMWControls',
|
||||||
|
name = 'Zoom3rdPerson_name',
|
||||||
|
description = 'Zoom3rdPerson_description',
|
||||||
|
type = input.ACTION_TYPE.Number,
|
||||||
|
defaultValue = 0,
|
||||||
|
}
|
||||||
|
|
||||||
local settings = require('scripts.omw.camera.settings').thirdPerson
|
local settings = require('scripts.omw.camera.settings').thirdPerson
|
||||||
local head_bobbing = require('scripts.omw.camera.head_bobbing')
|
local head_bobbing = require('scripts.omw.camera.head_bobbing')
|
||||||
local third_person = require('scripts.omw.camera.third_person')
|
local third_person = require('scripts.omw.camera.third_person')
|
||||||
|
@ -63,7 +81,7 @@ local previewTimer = 0
|
||||||
|
|
||||||
local function updatePOV(dt)
|
local function updatePOV(dt)
|
||||||
local switchLimit = 0.25
|
local switchLimit = 0.25
|
||||||
if input.isActionPressed(input.ACTION.TogglePOV) and Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) then
|
if input.getBooleanActionValue('TogglePOV') and Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) then
|
||||||
previewTimer = previewTimer + dt
|
previewTimer = previewTimer + dt
|
||||||
if primaryMode == MODE.ThirdPerson or previewTimer >= switchLimit then
|
if primaryMode == MODE.ThirdPerson or previewTimer >= switchLimit then
|
||||||
third_person.standingPreview = false
|
third_person.standingPreview = false
|
||||||
|
@ -117,18 +135,19 @@ local maxDistance = 800
|
||||||
|
|
||||||
local function zoom(delta)
|
local function zoom(delta)
|
||||||
if not Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) or
|
if not Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) or
|
||||||
not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or
|
not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or
|
||||||
camera.getMode() == MODE.Static or next(noZoom) then
|
camera.getMode() == MODE.Static or next(noZoom) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if camera.getMode() ~= MODE.FirstPerson then
|
if camera.getMode() ~= MODE.FirstPerson then
|
||||||
local obstacleDelta = third_person.preferredDistance - camera.getThirdPersonDistance()
|
local obstacleDelta = third_person.preferredDistance - camera.getThirdPersonDistance()
|
||||||
if delta > 0 and third_person.baseDistance == minDistance and
|
if delta > 0 and third_person.baseDistance == minDistance and
|
||||||
(camera.getMode() ~= MODE.Preview or third_person.standingPreview) and not next(noModeControl) then
|
(camera.getMode() ~= MODE.Preview or third_person.standingPreview) and not next(noModeControl) then
|
||||||
primaryMode = MODE.FirstPerson
|
primaryMode = MODE.FirstPerson
|
||||||
camera.setMode(primaryMode)
|
camera.setMode(primaryMode)
|
||||||
elseif delta > 0 or obstacleDelta < -delta then
|
elseif delta > 0 or obstacleDelta < -delta then
|
||||||
third_person.baseDistance = util.clamp(third_person.baseDistance - delta - obstacleDelta, minDistance, maxDistance)
|
third_person.baseDistance = util.clamp(third_person.baseDistance - delta - obstacleDelta, minDistance,
|
||||||
|
maxDistance)
|
||||||
end
|
end
|
||||||
elseif delta < 0 and not next(noModeControl) then
|
elseif delta < 0 and not next(noModeControl) then
|
||||||
primaryMode = MODE.ThirdPerson
|
primaryMode = MODE.ThirdPerson
|
||||||
|
@ -137,21 +156,10 @@ local function zoom(delta)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function applyControllerZoom(dt)
|
|
||||||
if input.isActionPressed(input.ACTION.TogglePOV) then
|
|
||||||
local triggerLeft = input.getAxisValue(input.CONTROLLER_AXIS.TriggerLeft)
|
|
||||||
local triggerRight = input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight)
|
|
||||||
local controllerZoom = (triggerRight - triggerLeft) * 100 * dt
|
|
||||||
if controllerZoom ~= 0 then
|
|
||||||
zoom(controllerZoom)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function updateStandingPreview()
|
local function updateStandingPreview()
|
||||||
local mode = camera.getMode()
|
local mode = camera.getMode()
|
||||||
if not previewIfStandStill or next(noStandingPreview)
|
if not previewIfStandStill or next(noStandingPreview)
|
||||||
or mode == MODE.FirstPerson or mode == MODE.Static or mode == MODE.Vanity then
|
or mode == MODE.FirstPerson or mode == MODE.Static or mode == MODE.Vanity then
|
||||||
third_person.standingPreview = false
|
third_person.standingPreview = false
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -184,7 +192,7 @@ local function updateIdleTimer(dt)
|
||||||
if not input.isIdle() then
|
if not input.isIdle() then
|
||||||
idleTimer = 0
|
idleTimer = 0
|
||||||
elseif self.controls.movement ~= 0 or self.controls.sideMovement ~= 0 or self.controls.jump or self.controls.use ~= 0 then
|
elseif self.controls.movement ~= 0 or self.controls.sideMovement ~= 0 or self.controls.jump or self.controls.use ~= 0 then
|
||||||
idleTimer = 0 -- also reset the timer in case of a scripted movement
|
idleTimer = 0 -- also reset the timer in case of a scripted movement
|
||||||
else
|
else
|
||||||
idleTimer = idleTimer + dt
|
idleTimer = idleTimer + dt
|
||||||
end
|
end
|
||||||
|
@ -205,7 +213,14 @@ local function onFrame(dt)
|
||||||
updateStandingPreview()
|
updateStandingPreview()
|
||||||
updateCrosshair()
|
updateCrosshair()
|
||||||
end
|
end
|
||||||
applyControllerZoom(dt)
|
|
||||||
|
do
|
||||||
|
local Zoom3rdPerson = input.getNumberActionValue('Zoom3rdPerson')
|
||||||
|
if Zoom3rdPerson ~= 0 then
|
||||||
|
zoom(Zoom3rdPerson)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
third_person.update(dt, smoothedSpeed)
|
third_person.update(dt, smoothedSpeed)
|
||||||
if not next(noHeadBobbing) then head_bobbing.update(dt, smoothedSpeed) end
|
if not next(noHeadBobbing) then head_bobbing.update(dt, smoothedSpeed) end
|
||||||
if slowViewChange then
|
if slowViewChange then
|
||||||
|
@ -312,15 +327,6 @@ return {
|
||||||
engineHandlers = {
|
engineHandlers = {
|
||||||
onUpdate = onUpdate,
|
onUpdate = onUpdate,
|
||||||
onFrame = onFrame,
|
onFrame = onFrame,
|
||||||
onInputAction = function(action)
|
|
||||||
if core.isWorldPaused() or I.UI.getMode() then return end
|
|
||||||
if action == input.ACTION.ZoomIn then
|
|
||||||
zoom(10)
|
|
||||||
elseif action == input.ACTION.ZoomOut then
|
|
||||||
zoom(-10)
|
|
||||||
end
|
|
||||||
move360.onInputAction(action)
|
|
||||||
end,
|
|
||||||
onTeleported = function()
|
onTeleported = function()
|
||||||
camera.instantTransition()
|
camera.instantTransition()
|
||||||
end,
|
end,
|
||||||
|
@ -329,7 +335,7 @@ return {
|
||||||
if data and data.distance then third_person.baseDistance = data.distance end
|
if data and data.distance then third_person.baseDistance = data.distance end
|
||||||
end,
|
end,
|
||||||
onSave = function()
|
onSave = function()
|
||||||
return {version = 0, distance = third_person.baseDistance}
|
return { version = 0, distance = third_person.baseDistance }
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,26 @@ local function turnOff()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function processZoom3rdPerson()
|
||||||
|
if
|
||||||
|
not Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) or
|
||||||
|
not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or
|
||||||
|
input.getBooleanActionValue('TogglePOV') or
|
||||||
|
not I.Camera.isModeControlEnabled()
|
||||||
|
then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local Zoom3rdPerson = input.getNumberActionValue('Zoom3rdPerson')
|
||||||
|
if Zoom3rdPerson > 0 and camera.getMode() == MODE.Preview
|
||||||
|
and I.Camera.getBaseThirdPersonDistance() == 30 then
|
||||||
|
self.controls.yawChange = camera.getYaw() - self.rotation:getYaw()
|
||||||
|
camera.setMode(MODE.FirstPerson)
|
||||||
|
elseif Zoom3rdPerson < 0 and camera.getMode() == MODE.FirstPerson then
|
||||||
|
camera.setMode(MODE.Preview)
|
||||||
|
I.Camera.setBaseThirdPersonDistance(30)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function M.onFrame(dt)
|
function M.onFrame(dt)
|
||||||
if core.isWorldPaused() then return end
|
if core.isWorldPaused() then return end
|
||||||
local newActive = M.enabled and Actor.getStance(self) == Actor.STANCE.Nothing
|
local newActive = M.enabled and Actor.getStance(self) == Actor.STANCE.Nothing
|
||||||
|
@ -39,9 +59,10 @@ function M.onFrame(dt)
|
||||||
turnOff()
|
turnOff()
|
||||||
end
|
end
|
||||||
if not active then return end
|
if not active then return end
|
||||||
|
processZoom3rdPerson()
|
||||||
if camera.getMode() == MODE.Static then return end
|
if camera.getMode() == MODE.Static then return end
|
||||||
if camera.getMode() == MODE.ThirdPerson then camera.setMode(MODE.Preview) end
|
if camera.getMode() == MODE.ThirdPerson then camera.setMode(MODE.Preview) end
|
||||||
if camera.getMode() == MODE.Preview and not input.isActionPressed(input.ACTION.TogglePOV) then
|
if camera.getMode() == MODE.Preview and not input.getBooleanActionValue('TogglePOV') then
|
||||||
camera.showCrosshair(camera.getFocalPreferredOffset():length() > 5)
|
camera.showCrosshair(camera.getFocalPreferredOffset():length() > 5)
|
||||||
local move = util.vector2(self.controls.sideMovement, self.controls.movement)
|
local move = util.vector2(self.controls.sideMovement, self.controls.movement)
|
||||||
local yawDelta = camera.getYaw() - self.rotation:getYaw()
|
local yawDelta = camera.getYaw() - self.rotation:getYaw()
|
||||||
|
@ -59,22 +80,4 @@ function M.onFrame(dt)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.onInputAction(action)
|
|
||||||
if not active or core.isWorldPaused() or
|
|
||||||
not Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) or
|
|
||||||
not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or
|
|
||||||
input.isActionPressed(input.ACTION.TogglePOV) or
|
|
||||||
not I.Camera.isModeControlEnabled() then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if action == input.ACTION.ZoomIn and camera.getMode() == MODE.Preview
|
|
||||||
and I.Camera.getBaseThirdPersonDistance() == 30 then
|
|
||||||
self.controls.yawChange = camera.getYaw() - self.rotation:getYaw()
|
|
||||||
camera.setMode(MODE.FirstPerson)
|
|
||||||
elseif action == input.ACTION.ZoomOut and camera.getMode() == MODE.FirstPerson then
|
|
||||||
camera.setMode(MODE.Preview)
|
|
||||||
I.Camera.setBaseThirdPersonDistance(30)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|
255
files/data/scripts/omw/input/actionbindings.lua
Normal file
255
files/data/scripts/omw/input/actionbindings.lua
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
local core = require('openmw.core')
|
||||||
|
local input = require('openmw.input')
|
||||||
|
local util = require('openmw.util')
|
||||||
|
local async = require('openmw.async')
|
||||||
|
local storage = require('openmw.storage')
|
||||||
|
local ui = require('openmw.ui')
|
||||||
|
|
||||||
|
local I = require('openmw.interfaces')
|
||||||
|
|
||||||
|
local actionPressHandlers = {}
|
||||||
|
local function onActionPress(id, handler)
|
||||||
|
actionPressHandlers[id] = actionPressHandlers[id] or {}
|
||||||
|
table.insert(actionPressHandlers[id], handler)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function bindHold(key, actionId)
|
||||||
|
input.bindAction(key, async:callback(function()
|
||||||
|
return input.isActionPressed(actionId)
|
||||||
|
end), {})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function bindMovement(key, actionId, axisId, direction)
|
||||||
|
input.bindAction(key, async:callback(function()
|
||||||
|
local actionActive = input.isActionPressed(actionId)
|
||||||
|
local axisActive = input.getAxisValue(axisId) * direction > 0
|
||||||
|
return (actionActive or axisActive) and 1 or 0
|
||||||
|
end), {})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function bindTrigger(key, actionid)
|
||||||
|
onActionPress(actionid, function()
|
||||||
|
input.activateTrigger(key)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
bindTrigger('AlwaysRun', input.ACTION.AlwaysRun)
|
||||||
|
bindTrigger('ToggleSneak', input.ACTION.Sneak)
|
||||||
|
bindTrigger('ToggleWeapon', input.ACTION.ToggleWeapon)
|
||||||
|
bindTrigger('ToggleSpell', input.ACTION.ToggleSpell)
|
||||||
|
bindTrigger('Jump', input.ACTION.Jump)
|
||||||
|
bindTrigger('AutoMove', input.ACTION.AutoMove)
|
||||||
|
bindTrigger('Inventory', input.ACTION.Inventory)
|
||||||
|
bindTrigger('Journal', input.ACTION.Journal)
|
||||||
|
bindTrigger('QuickKeysMenu', input.ACTION.QuickKeysMenu)
|
||||||
|
|
||||||
|
bindHold('TogglePOV', input.ACTION.TogglePOV)
|
||||||
|
bindHold('Sneak', input.ACTION.Sneak)
|
||||||
|
|
||||||
|
bindHold('Run', input.ACTION.Run)
|
||||||
|
input.bindAction('Run', async:callback(function(_, value)
|
||||||
|
local controllerInput = util.vector2(
|
||||||
|
input.getAxisValue(input.CONTROLLER_AXIS.MoveForwardBackward),
|
||||||
|
input.getAxisValue(input.CONTROLLER_AXIS.MoveLeftRight)
|
||||||
|
):length2()
|
||||||
|
return value or controllerInput > 0.25
|
||||||
|
end), {})
|
||||||
|
|
||||||
|
input.bindAction('Use', async:callback(function()
|
||||||
|
-- The value "0.6" shouldn't exceed the triggering threshold in BindingsManager::actionValueChanged.
|
||||||
|
-- TODO: Move more logic from BindingsManager to Lua and consider to make this threshold configurable.
|
||||||
|
return input.isActionPressed(input.ACTION.Use) or input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight) >= 0.6
|
||||||
|
end), {})
|
||||||
|
|
||||||
|
bindMovement('MoveBackward', input.ACTION.MoveBackward, input.CONTROLLER_AXIS.MoveForwardBackward, 1)
|
||||||
|
bindMovement('MoveForward', input.ACTION.MoveForward, input.CONTROLLER_AXIS.MoveForwardBackward, -1)
|
||||||
|
bindMovement('MoveRight', input.ACTION.MoveRight, input.CONTROLLER_AXIS.MoveLeftRight, 1)
|
||||||
|
bindMovement('MoveLeft', input.ACTION.MoveLeft, input.CONTROLLER_AXIS.MoveLeftRight, -1)
|
||||||
|
|
||||||
|
do
|
||||||
|
local zoomInOut = 0
|
||||||
|
onActionPress(input.ACTION.ZoomIn, function()
|
||||||
|
zoomInOut = zoomInOut + 1
|
||||||
|
end)
|
||||||
|
onActionPress(input.ACTION.ZoomOut, function()
|
||||||
|
zoomInOut = zoomInOut - 1
|
||||||
|
end)
|
||||||
|
input.bindAction('Zoom3rdPerson', async:callback(function(dt, _, togglePOV)
|
||||||
|
local Zoom3rdPerson = zoomInOut * 10
|
||||||
|
if togglePOV then
|
||||||
|
local triggerLeft = input.getAxisValue(input.CONTROLLER_AXIS.TriggerLeft)
|
||||||
|
local triggerRight = input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight)
|
||||||
|
local controllerZoom = (triggerRight - triggerLeft) * 100 * dt
|
||||||
|
Zoom3rdPerson = Zoom3rdPerson + controllerZoom
|
||||||
|
end
|
||||||
|
zoomInOut = 0
|
||||||
|
return Zoom3rdPerson
|
||||||
|
end), { 'TogglePOV' })
|
||||||
|
end
|
||||||
|
|
||||||
|
local bindingSection = storage.playerSection('OMWInputBindings')
|
||||||
|
|
||||||
|
local keyboardPresses = {}
|
||||||
|
local keybordHolds = {}
|
||||||
|
local boundActions = {}
|
||||||
|
|
||||||
|
local function bindAction(action)
|
||||||
|
if boundActions[action] then return end
|
||||||
|
boundActions[action] = true
|
||||||
|
input.bindAction(action, async:callback(function()
|
||||||
|
if keybordHolds[action] then
|
||||||
|
for _, binding in pairs(keybordHolds[action]) do
|
||||||
|
if input.isKeyPressed(binding.code) then return true end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end), {})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function registerBinding(binding, id)
|
||||||
|
if not input.actions[binding.key] and not input.triggers[binding.key] then
|
||||||
|
print(string.format('Skipping binding for unknown action or trigger: "%s"', binding.key))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if binding.type == 'keyboardPress' then
|
||||||
|
local bindings = keyboardPresses[binding.code] or {}
|
||||||
|
bindings[id] = binding
|
||||||
|
keyboardPresses[binding.code] = bindings
|
||||||
|
elseif binding.type == 'keyboardHold' then
|
||||||
|
local bindings = keybordHolds[binding.key] or {}
|
||||||
|
bindings[id] = binding
|
||||||
|
keybordHolds[binding.key] = bindings
|
||||||
|
bindAction(binding.key)
|
||||||
|
else
|
||||||
|
error('Unknown binding type "' .. binding.type .. '"')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function clearBinding(id)
|
||||||
|
for _, boundTriggers in pairs(keyboardPresses) do
|
||||||
|
boundTriggers[id] = nil
|
||||||
|
end
|
||||||
|
for _, boundKeys in pairs(keybordHolds) do
|
||||||
|
boundKeys[id] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function updateBinding(id, binding)
|
||||||
|
bindingSection:set(id, binding)
|
||||||
|
clearBinding(id)
|
||||||
|
if binding ~= nil then
|
||||||
|
registerBinding(binding, id)
|
||||||
|
end
|
||||||
|
return id
|
||||||
|
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
|
||||||
|
|
||||||
|
local function init()
|
||||||
|
for id, binding in pairs(bindingSection:asTable()) do
|
||||||
|
registerBinding(binding, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
engineHandlers = {
|
||||||
|
onFrame = function()
|
||||||
|
if not initiated then
|
||||||
|
initiated = true
|
||||||
|
init()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
onInputAction = function(id)
|
||||||
|
if not actionPressHandlers[id] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for _, handler in ipairs(actionPressHandlers[id]) do
|
||||||
|
handler()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
onKeyPress = function(e)
|
||||||
|
local bindings = keyboardPresses[e.code]
|
||||||
|
if bindings then
|
||||||
|
for _, binding in pairs(bindings) do
|
||||||
|
input.activateTrigger(binding.key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
}
|
92
files/data/scripts/omw/input/smoothmovement.lua
Normal file
92
files/data/scripts/omw/input/smoothmovement.lua
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
local input = require('openmw.input')
|
||||||
|
local util = require('openmw.util')
|
||||||
|
local async = require('openmw.async')
|
||||||
|
local storage = require('openmw.storage')
|
||||||
|
local types = require('openmw.types')
|
||||||
|
local self = require('openmw.self')
|
||||||
|
|
||||||
|
local NPC = types.NPC
|
||||||
|
|
||||||
|
local moveActions = {
|
||||||
|
'MoveForward',
|
||||||
|
'MoveBackward',
|
||||||
|
'MoveLeft',
|
||||||
|
'MoveRight'
|
||||||
|
}
|
||||||
|
for _, key in ipairs(moveActions) do
|
||||||
|
local smoothKey = 'Smooth' .. key
|
||||||
|
input.registerAction {
|
||||||
|
key = smoothKey,
|
||||||
|
l10n = 'OMWControls',
|
||||||
|
name = smoothKey .. '_name',
|
||||||
|
description = smoothKey .. '_description',
|
||||||
|
type = input.ACTION_TYPE.Range,
|
||||||
|
defaultValue = 0,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local settings = storage.playerSection('SettingsOMWControls')
|
||||||
|
|
||||||
|
local function shouldAlwaysRun(actor)
|
||||||
|
return actor.controls.sneak or not NPC.isOnGround(actor) or NPC.isSwimming(actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function remapToWalkRun(actor, inputMovement)
|
||||||
|
if shouldAlwaysRun(actor) then
|
||||||
|
return true, inputMovement
|
||||||
|
end
|
||||||
|
local normalizedInput, inputSpeed = inputMovement:normalize()
|
||||||
|
local switchPoint = 0.5
|
||||||
|
if inputSpeed < switchPoint then
|
||||||
|
return false, inputMovement * 2
|
||||||
|
else
|
||||||
|
local matchWalkingSpeed = NPC.getWalkSpeed(actor) / NPC.getRunSpeed(actor)
|
||||||
|
local runSpeedRatio = 2 * (inputSpeed - switchPoint) * (1 - matchWalkingSpeed) + matchWalkingSpeed
|
||||||
|
return true, normalizedInput * math.min(1, runSpeedRatio)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function computeSmoothMovement()
|
||||||
|
local controllerInput = util.vector2(
|
||||||
|
input.getAxisValue(input.CONTROLLER_AXIS.MoveForwardBackward),
|
||||||
|
input.getAxisValue(input.CONTROLLER_AXIS.MoveLeftRight)
|
||||||
|
)
|
||||||
|
return remapToWalkRun(self, controllerInput)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function bindSmoothMove(key, axis, direction)
|
||||||
|
local smoothKey = 'Smooth' .. key
|
||||||
|
input.bindAction(smoothKey, async:callback(function()
|
||||||
|
local _, movement = computeSmoothMovement()
|
||||||
|
return math.max(direction * movement[axis], 0)
|
||||||
|
end), {})
|
||||||
|
input.bindAction(key, async:callback(function(_, standardMovement, smoothMovement)
|
||||||
|
if not settings:get('smoothControllerMovement') then
|
||||||
|
return standardMovement
|
||||||
|
end
|
||||||
|
|
||||||
|
if smoothMovement > 0 then
|
||||||
|
return smoothMovement
|
||||||
|
else
|
||||||
|
return standardMovement
|
||||||
|
end
|
||||||
|
end), { smoothKey })
|
||||||
|
end
|
||||||
|
|
||||||
|
bindSmoothMove('MoveForward', 'x', -1)
|
||||||
|
bindSmoothMove('MoveBackward', 'x', 1)
|
||||||
|
bindSmoothMove('MoveRight', 'y', 1)
|
||||||
|
bindSmoothMove('MoveLeft', 'y', -1)
|
||||||
|
|
||||||
|
input.bindAction('Run', async:callback(function(_, run)
|
||||||
|
if not settings:get('smoothControllerMovement') then
|
||||||
|
return run
|
||||||
|
end
|
||||||
|
local smoothRun, movement = computeSmoothMovement()
|
||||||
|
if movement:length2() > 0 then
|
||||||
|
-- ignore always run
|
||||||
|
return smoothRun ~= settings:get('alwaysRun')
|
||||||
|
else
|
||||||
|
return run
|
||||||
|
end
|
||||||
|
end), {})
|
|
@ -1,12 +1,12 @@
|
||||||
local core = require('openmw.core')
|
local core = require('openmw.core')
|
||||||
local input = require('openmw.input')
|
local input = require('openmw.input')
|
||||||
local self = require('openmw.self')
|
local self = require('openmw.self')
|
||||||
local util = require('openmw.util')
|
local storage = require('openmw.storage')
|
||||||
local ui = require('openmw.ui')
|
local ui = require('openmw.ui')
|
||||||
|
local async = require('openmw.async')
|
||||||
local Actor = require('openmw.types').Actor
|
local Actor = require('openmw.types').Actor
|
||||||
local Player = require('openmw.types').Player
|
local Player = require('openmw.types').Player
|
||||||
|
|
||||||
local storage = require('openmw.storage')
|
|
||||||
local I = require('openmw.interfaces')
|
local I = require('openmw.interfaces')
|
||||||
|
|
||||||
local settingsGroup = 'SettingsOMWControls'
|
local settingsGroup = 'SettingsOMWControls'
|
||||||
|
@ -16,16 +16,16 @@ local function boolSetting(key, default)
|
||||||
key = key,
|
key = key,
|
||||||
renderer = 'checkbox',
|
renderer = 'checkbox',
|
||||||
name = key,
|
name = key,
|
||||||
description = key..'Description',
|
description = key .. 'Description',
|
||||||
default = default,
|
default = default,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
I.Settings.registerPage({
|
I.Settings.registerPage({
|
||||||
key = 'OMWControls',
|
key = 'OMWControls',
|
||||||
l10n = 'OMWControls',
|
l10n = 'OMWControls',
|
||||||
name = 'ControlsPage',
|
name = 'ControlsPage',
|
||||||
description = 'ControlsPageDescription',
|
description = 'ControlsPageDescription',
|
||||||
})
|
})
|
||||||
|
|
||||||
I.Settings.registerGroup({
|
I.Settings.registerGroup({
|
||||||
|
@ -36,95 +36,68 @@ I.Settings.registerGroup({
|
||||||
permanentStorage = true,
|
permanentStorage = true,
|
||||||
settings = {
|
settings = {
|
||||||
boolSetting('alwaysRun', false),
|
boolSetting('alwaysRun', false),
|
||||||
boolSetting('toggleSneak', false),
|
boolSetting('toggleSneak', false), -- TODO: consider removing this setting when we have the advanced binding UI
|
||||||
|
boolSetting('smoothControllerMovement', true),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
local settings = storage.playerSection(settingsGroup)
|
local settings = storage.playerSection('SettingsOMWControls')
|
||||||
|
|
||||||
local attemptJump = false
|
do
|
||||||
local startAttack = false
|
local rangeActions = {
|
||||||
local autoMove = false
|
'MoveForward',
|
||||||
local movementControlsOverridden = false
|
'MoveBackward',
|
||||||
local combatControlsOverridden = false
|
'MoveLeft',
|
||||||
local uiControlsOverridden = false
|
'MoveRight'
|
||||||
|
}
|
||||||
|
for _, key in ipairs(rangeActions) do
|
||||||
|
input.registerAction {
|
||||||
|
key = key,
|
||||||
|
l10n = 'OMWControls',
|
||||||
|
name = key .. '_name',
|
||||||
|
description = key .. '_description',
|
||||||
|
type = input.ACTION_TYPE.Range,
|
||||||
|
defaultValue = 0,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
local function processMovement()
|
local booleanActions = {
|
||||||
local controllerMovement = -input.getAxisValue(input.CONTROLLER_AXIS.MoveForwardBackward)
|
'Use',
|
||||||
local controllerSideMovement = input.getAxisValue(input.CONTROLLER_AXIS.MoveLeftRight)
|
'Run',
|
||||||
if controllerMovement ~= 0 or controllerSideMovement ~= 0 then
|
'Sneak',
|
||||||
-- controller movement
|
}
|
||||||
if util.vector2(controllerMovement, controllerSideMovement):length2() < 0.25
|
for _, key in ipairs(booleanActions) do
|
||||||
and not self.controls.sneak and Actor.isOnGround(self) and not Actor.isSwimming(self) then
|
input.registerAction {
|
||||||
self.controls.run = false
|
key = key,
|
||||||
self.controls.movement = controllerMovement * 2
|
l10n = 'OMWControls',
|
||||||
self.controls.sideMovement = controllerSideMovement * 2
|
name = key .. '_name',
|
||||||
else
|
description = key .. '_description',
|
||||||
self.controls.run = true
|
type = input.ACTION_TYPE.Boolean,
|
||||||
self.controls.movement = controllerMovement
|
defaultValue = false,
|
||||||
self.controls.sideMovement = controllerSideMovement
|
}
|
||||||
end
|
|
||||||
else
|
|
||||||
-- keyboard movement
|
|
||||||
self.controls.movement = 0
|
|
||||||
self.controls.sideMovement = 0
|
|
||||||
if input.isActionPressed(input.ACTION.MoveLeft) then
|
|
||||||
self.controls.sideMovement = self.controls.sideMovement - 1
|
|
||||||
end
|
|
||||||
if input.isActionPressed(input.ACTION.MoveRight) then
|
|
||||||
self.controls.sideMovement = self.controls.sideMovement + 1
|
|
||||||
end
|
|
||||||
if input.isActionPressed(input.ACTION.MoveBackward) then
|
|
||||||
self.controls.movement = self.controls.movement - 1
|
|
||||||
end
|
|
||||||
if input.isActionPressed(input.ACTION.MoveForward) then
|
|
||||||
self.controls.movement = self.controls.movement + 1
|
|
||||||
end
|
|
||||||
self.controls.run = input.isActionPressed(input.ACTION.Run) ~= settings:get('alwaysRun')
|
|
||||||
end
|
end
|
||||||
if self.controls.movement ~= 0 or not Actor.canMove(self) then
|
|
||||||
autoMove = false
|
|
||||||
elseif autoMove then
|
|
||||||
self.controls.movement = 1
|
|
||||||
end
|
|
||||||
self.controls.jump = attemptJump and Player.getControlSwitch(self, Player.CONTROL_SWITCH.Jumping)
|
|
||||||
if not settings:get('toggleSneak') then
|
|
||||||
self.controls.sneak = input.isActionPressed(input.ACTION.Sneak)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function processAttacking()
|
local triggers = {
|
||||||
if startAttack then
|
'Jump',
|
||||||
self.controls.use = 1
|
'AutoMove',
|
||||||
elseif Actor.stance(self) == Actor.STANCE.Spell then
|
'ToggleWeapon',
|
||||||
self.controls.use = 0
|
'ToggleSpell',
|
||||||
elseif input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight) < 0.6
|
'AlwaysRun',
|
||||||
and not input.isActionPressed(input.ACTION.Use) then
|
'ToggleSneak',
|
||||||
-- The value "0.6" shouldn't exceed the triggering threshold in BindingsManager::actionValueChanged.
|
'Inventory',
|
||||||
-- TODO: Move more logic from BindingsManager to Lua and consider to make this threshold configurable.
|
'Journal',
|
||||||
self.controls.use = 0
|
'QuickKeysMenu',
|
||||||
|
}
|
||||||
|
for _, key in ipairs(triggers) do
|
||||||
|
input.registerTrigger {
|
||||||
|
key = key,
|
||||||
|
l10n = 'OMWControls',
|
||||||
|
name = key .. '_name',
|
||||||
|
description = key .. '_description',
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function onFrame(dt)
|
|
||||||
local controlsAllowed = Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls)
|
|
||||||
and not core.isWorldPaused() and not I.UI.getMode()
|
|
||||||
if not movementControlsOverridden then
|
|
||||||
if controlsAllowed then
|
|
||||||
processMovement()
|
|
||||||
else
|
|
||||||
self.controls.movement = 0
|
|
||||||
self.controls.sideMovement = 0
|
|
||||||
self.controls.jump = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if controlsAllowed and not combatControlsOverridden then
|
|
||||||
processAttacking()
|
|
||||||
end
|
|
||||||
attemptJump = false
|
|
||||||
startAttack = false
|
|
||||||
end
|
|
||||||
|
|
||||||
local function checkNotWerewolf()
|
local function checkNotWerewolf()
|
||||||
if Player.isWerewolf(self) then
|
if Player.isWerewolf(self) then
|
||||||
ui.showMessage(core.getGMST('sWerewolfRefusal'))
|
ui.showMessage(core.getGMST('sWerewolfRefusal'))
|
||||||
|
@ -139,68 +112,163 @@ local function isJournalAllowed()
|
||||||
return I.UI.getWindowsForMode(I.UI.MODE.Interface)[I.UI.WINDOW.Magic]
|
return I.UI.getWindowsForMode(I.UI.MODE.Interface)[I.UI.WINDOW.Magic]
|
||||||
end
|
end
|
||||||
|
|
||||||
local function onInputAction(action)
|
local movementControlsOverridden = false
|
||||||
if not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) then
|
|
||||||
return
|
local autoMove = false
|
||||||
|
local function processMovement()
|
||||||
|
local movement = input.getRangeActionValue('MoveForward') - input.getRangeActionValue('MoveBackward')
|
||||||
|
local sideMovement = input.getRangeActionValue('MoveRight') - input.getRangeActionValue('MoveLeft')
|
||||||
|
local run = input.getBooleanActionValue('Run') ~= settings:get('alwaysRun')
|
||||||
|
|
||||||
|
if movement ~= 0 or not Actor.canMove(self) then
|
||||||
|
autoMove = false
|
||||||
|
elseif autoMove then
|
||||||
|
movement = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
if not uiControlsOverridden then
|
self.controls.movement = movement
|
||||||
if action == input.ACTION.Inventory then
|
self.controls.sideMovement = sideMovement
|
||||||
if I.UI.getMode() == nil then
|
self.controls.run = run
|
||||||
I.UI.setMode(I.UI.MODE.Interface)
|
|
||||||
elseif I.UI.getMode() == I.UI.MODE.Interface or I.UI.getMode() == I.UI.MODE.Container then
|
if not settings:get('toggleSneak') then
|
||||||
I.UI.removeMode(I.UI.getMode())
|
self.controls.sneak = input.getBooleanActionValue('Sneak')
|
||||||
end
|
end
|
||||||
elseif action == input.ACTION.Journal then
|
end
|
||||||
if I.UI.getMode() == I.UI.MODE.Journal then
|
|
||||||
I.UI.removeMode(I.UI.MODE.Journal)
|
local function controlsAllowed()
|
||||||
elseif isJournalAllowed() then
|
return not core.isWorldPaused()
|
||||||
I.UI.addMode(I.UI.MODE.Journal)
|
and Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls)
|
||||||
end
|
and not I.UI.getMode()
|
||||||
elseif action == input.ACTION.QuickKeysMenu then
|
end
|
||||||
if I.UI.getMode() == I.UI.MODE.QuickKeysMenu then
|
|
||||||
I.UI.removeMode(I.UI.MODE.QuickKeysMenu)
|
local function movementAllowed()
|
||||||
elseif checkNotWerewolf() and Player.isCharGenFinished(self) then
|
return controlsAllowed() and not movementControlsOverridden
|
||||||
I.UI.addMode(I.UI.MODE.QuickKeysMenu)
|
end
|
||||||
end
|
|
||||||
|
input.registerTriggerHandler('Jump', async:callback(function()
|
||||||
|
if not movementAllowed() then return end
|
||||||
|
self.controls.jump = Player.getControlSwitch(self, Player.CONTROL_SWITCH.Jumping)
|
||||||
|
end))
|
||||||
|
|
||||||
|
input.registerTriggerHandler('ToggleSneak', async:callback(function()
|
||||||
|
if not movementAllowed() then return end
|
||||||
|
if settings:get('toggleSneak') then
|
||||||
|
self.controls.sneak = not self.controls.sneak
|
||||||
|
end
|
||||||
|
end))
|
||||||
|
|
||||||
|
input.registerTriggerHandler('AlwaysRun', async:callback(function()
|
||||||
|
if not movementAllowed() then return end
|
||||||
|
settings:set('alwaysRun', not settings:get('alwaysRun'))
|
||||||
|
end))
|
||||||
|
|
||||||
|
input.registerTriggerHandler('AutoMove', async:callback(function()
|
||||||
|
if not movementAllowed() then return end
|
||||||
|
autoMove = not autoMove
|
||||||
|
end))
|
||||||
|
|
||||||
|
local combatControlsOverridden = false
|
||||||
|
|
||||||
|
local function combatAllowed()
|
||||||
|
return controlsAllowed() and not combatControlsOverridden
|
||||||
|
end
|
||||||
|
|
||||||
|
input.registerTriggerHandler('ToggleSpell', async:callback(function()
|
||||||
|
if not combatAllowed() then return end
|
||||||
|
if Actor.stance(self) == Actor.STANCE.Spell then
|
||||||
|
Actor.setStance(self, Actor.STANCE.Nothing)
|
||||||
|
elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Magic) then
|
||||||
|
if checkNotWerewolf() then
|
||||||
|
Actor.setStance(self, Actor.STANCE.Spell)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end))
|
||||||
|
|
||||||
if core.isWorldPaused() or I.UI.getMode() then
|
input.registerTriggerHandler('ToggleWeapon', async:callback(function()
|
||||||
return
|
if not combatAllowed() then return end
|
||||||
|
if Actor.stance(self) == Actor.STANCE.Weapon then
|
||||||
|
Actor.setStance(self, Actor.STANCE.Nothing)
|
||||||
|
elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Fighting) then
|
||||||
|
Actor.setStance(self, Actor.STANCE.Weapon)
|
||||||
end
|
end
|
||||||
|
end))
|
||||||
|
|
||||||
if action == input.ACTION.Jump then
|
local startUse = false
|
||||||
attemptJump = true
|
input.registerActionHandler('Use', async:callback(function(value)
|
||||||
elseif action == input.ACTION.Use then
|
if value then startUse = true end
|
||||||
startAttack = Actor.stance(self) ~= Actor.STANCE.Nothing
|
end))
|
||||||
elseif action == input.ACTION.AutoMove and not movementControlsOverridden then
|
local function processAttacking()
|
||||||
autoMove = not autoMove
|
if Actor.stance(self) == Actor.STANCE.Spell then
|
||||||
elseif action == input.ACTION.AlwaysRun and not movementControlsOverridden then
|
self.controls.use = startUse and 1 or 0
|
||||||
settings:set('alwaysRun', not settings:get('alwaysRun'))
|
else
|
||||||
elseif action == input.ACTION.Sneak and not movementControlsOverridden then
|
self.controls.use = input.getBooleanActionValue('Use') and 1 or 0
|
||||||
if settings:get('toggleSneak') then
|
end
|
||||||
self.controls.sneak = not self.controls.sneak
|
startUse = false
|
||||||
end
|
end
|
||||||
elseif action == input.ACTION.ToggleSpell and not combatControlsOverridden then
|
|
||||||
if Actor.stance(self) == Actor.STANCE.Spell then
|
local uiControlsOverridden = false
|
||||||
Actor.setStance(self, Actor.STANCE.Nothing)
|
|
||||||
elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Magic) then
|
input.registerTriggerHandler('ToggleWeapon', async:callback(function()
|
||||||
if checkNotWerewolf() then
|
if not combatAllowed() then return end
|
||||||
Actor.setStance(self, Actor.STANCE.Spell)
|
if Actor.stance(self) == Actor.STANCE.Weapon then
|
||||||
end
|
Actor.setStance(self, Actor.STANCE.Nothing)
|
||||||
end
|
elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Fighting) then
|
||||||
elseif action == input.ACTION.ToggleWeapon and not combatControlsOverridden then
|
Actor.setStance(self, Actor.STANCE.Weapon)
|
||||||
if Actor.stance(self) == Actor.STANCE.Weapon then
|
end
|
||||||
Actor.setStance(self, Actor.STANCE.Nothing)
|
end))
|
||||||
elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Fighting) then
|
|
||||||
Actor.setStance(self, Actor.STANCE.Weapon)
|
|
||||||
end
|
local function uiAllowed()
|
||||||
|
return Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) and not uiControlsOverridden
|
||||||
|
end
|
||||||
|
|
||||||
|
input.registerTriggerHandler('Inventory', async:callback(function()
|
||||||
|
if not uiAllowed() then return end
|
||||||
|
|
||||||
|
if I.UI.getMode() == nil then
|
||||||
|
I.UI.setMode(I.UI.MODE.Interface)
|
||||||
|
elseif I.UI.getMode() == I.UI.MODE.Interface or I.UI.getMode() == I.UI.MODE.Container then
|
||||||
|
I.UI.removeMode(I.UI.getMode())
|
||||||
|
end
|
||||||
|
end))
|
||||||
|
|
||||||
|
input.registerTriggerHandler('Journal', async:callback(function()
|
||||||
|
if not uiAllowed() then return end
|
||||||
|
|
||||||
|
if I.UI.getMode() == I.UI.MODE.Journal then
|
||||||
|
I.UI.removeMode(I.UI.MODE.Journal)
|
||||||
|
elseif isJournalAllowed() then
|
||||||
|
I.UI.addMode(I.UI.MODE.Journal)
|
||||||
|
end
|
||||||
|
end))
|
||||||
|
|
||||||
|
input.registerTriggerHandler('QuickKeysMenu', async:callback(function()
|
||||||
|
if not uiAllowed() then return end
|
||||||
|
|
||||||
|
if I.UI.getMode() == I.UI.MODE.QuickKeysMenu then
|
||||||
|
I.UI.removeMode(I.UI.MODE.QuickKeysMenu)
|
||||||
|
elseif checkNotWerewolf() and Player.isCharGenFinished(self) then
|
||||||
|
I.UI.addMode(I.UI.MODE.QuickKeysMenu)
|
||||||
|
end
|
||||||
|
end))
|
||||||
|
|
||||||
|
local function onFrame(_)
|
||||||
|
if movementAllowed() then
|
||||||
|
processMovement()
|
||||||
|
elseif not movementControlsOverridden then
|
||||||
|
self.controls.movement = 0
|
||||||
|
self.controls.sideMovement = 0
|
||||||
|
self.controls.jump = false
|
||||||
|
end
|
||||||
|
if combatAllowed() then
|
||||||
|
processAttacking()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function onSave()
|
local function onSave()
|
||||||
return {sneaking = self.controls.sneak}
|
return {
|
||||||
|
sneaking = self.controls.sneak
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
local function onLoad(data)
|
local function onLoad(data)
|
||||||
|
@ -211,7 +279,6 @@ end
|
||||||
return {
|
return {
|
||||||
engineHandlers = {
|
engineHandlers = {
|
||||||
onFrame = onFrame,
|
onFrame = onFrame,
|
||||||
onInputAction = onInputAction,
|
|
||||||
onSave = onSave,
|
onSave = onSave,
|
||||||
onLoad = onLoad,
|
onLoad = onLoad,
|
||||||
},
|
},
|
||||||
|
@ -242,4 +309,3 @@ return {
|
||||||
overrideUiControls = function(v) uiControlsOverridden = v end,
|
overrideUiControls = function(v) uiControlsOverridden = v end,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,7 @@
|
||||||
-- @return #boolean
|
-- @return #boolean
|
||||||
|
|
||||||
---
|
---
|
||||||
-- Is a specific control currently pressed.
|
-- (DEPRECATED, use getBooleanActionValue) Input bindings can be changed ingame using Options/Controls menu.
|
||||||
-- Input bindings can be changed ingame using Options/Controls menu.
|
|
||||||
-- @function [parent=#input] isActionPressed
|
-- @function [parent=#input] isActionPressed
|
||||||
-- @param #number actionId One of @{openmw.input#ACTION}
|
-- @param #number actionId One of @{openmw.input#ACTION}
|
||||||
-- @return #boolean
|
-- @return #boolean
|
||||||
|
@ -108,6 +107,7 @@
|
||||||
-- @field [parent=#input] #CONTROL_SWITCH CONTROL_SWITCH
|
-- @field [parent=#input] #CONTROL_SWITCH CONTROL_SWITCH
|
||||||
|
|
||||||
---
|
---
|
||||||
|
-- (DEPRECATED, use actions with matching keys)
|
||||||
-- @type ACTION
|
-- @type ACTION
|
||||||
-- @field [parent=#ACTION] #number GameMenu
|
-- @field [parent=#ACTION] #number GameMenu
|
||||||
-- @field [parent=#ACTION] #number Screenshot
|
-- @field [parent=#ACTION] #number Screenshot
|
||||||
|
@ -153,7 +153,7 @@
|
||||||
-- @field [parent=#ACTION] #number TogglePostProcessorHUD
|
-- @field [parent=#ACTION] #number TogglePostProcessorHUD
|
||||||
|
|
||||||
---
|
---
|
||||||
-- Values that can be used with isActionPressed.
|
-- (DEPRECATED, use getBooleanActionValue) Values that can be used with isActionPressed.
|
||||||
-- @field [parent=#input] #ACTION ACTION
|
-- @field [parent=#input] #ACTION ACTION
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -187,10 +187,10 @@
|
||||||
-- @field [parent=#CONTROLLER_AXIS] #number RightY Right stick vertical axis (from -1 to 1)
|
-- @field [parent=#CONTROLLER_AXIS] #number RightY Right stick vertical axis (from -1 to 1)
|
||||||
-- @field [parent=#CONTROLLER_AXIS] #number TriggerLeft Left trigger (from 0 to 1)
|
-- @field [parent=#CONTROLLER_AXIS] #number TriggerLeft Left trigger (from 0 to 1)
|
||||||
-- @field [parent=#CONTROLLER_AXIS] #number TriggerRight Right trigger (from 0 to 1)
|
-- @field [parent=#CONTROLLER_AXIS] #number TriggerRight Right trigger (from 0 to 1)
|
||||||
-- @field [parent=#CONTROLLER_AXIS] #number LookUpDown View direction vertical axis (RightY by default, can be mapped to another axis in Options/Controls menu)
|
-- @field [parent=#CONTROLLER_AXIS] #number LookUpDown (DEPRECATED, use the LookUpDown action) View direction vertical axis (RightY by default, can be mapped to another axis in Options/Controls menu)
|
||||||
-- @field [parent=#CONTROLLER_AXIS] #number LookLeftRight View direction horizontal axis (RightX by default, can be mapped to another axis in Options/Controls menu)
|
-- @field [parent=#CONTROLLER_AXIS] #number LookLeftRight (DEPRECATED, use the LookLeftRight action) View direction horizontal axis (RightX by default, can be mapped to another axis in Options/Controls menu)
|
||||||
-- @field [parent=#CONTROLLER_AXIS] #number MoveForwardBackward Movement forward/backward (LeftY by default, can be mapped to another axis in Options/Controls menu)
|
-- @field [parent=#CONTROLLER_AXIS] #number MoveForwardBackward (DEPRECATED, use the MoveForwardBackward action) Movement forward/backward (LeftY by default, can be mapped to another axis in Options/Controls menu)
|
||||||
-- @field [parent=#CONTROLLER_AXIS] #number MoveLeftRight Side movement (LeftX by default, can be mapped to another axis in Options/Controls menu)
|
-- @field [parent=#CONTROLLER_AXIS] #number MoveLeftRight (DEPRECATED, use the MoveLeftRight action) Side movement (LeftX by default, can be mapped to another axis in Options/Controls menu)
|
||||||
|
|
||||||
---
|
---
|
||||||
-- Values that can be used with getAxisValue.
|
-- Values that can be used with getAxisValue.
|
||||||
|
@ -327,4 +327,105 @@
|
||||||
-- @field [parent=#TouchEvent] openmw.util#Vector2 position Relative position on the touch device (0 to 1 from top left corner),
|
-- @field [parent=#TouchEvent] openmw.util#Vector2 position Relative position on the touch device (0 to 1 from top left corner),
|
||||||
-- @field [parent=#TouchEvent] #number pressure Pressure of the finger.
|
-- @field [parent=#TouchEvent] #number pressure Pressure of the finger.
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @type ActionType
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @type ACTION_TYPE
|
||||||
|
-- @field #ActionType Boolean Input action with value of true or false
|
||||||
|
-- @field #ActionType Number Input action with a numeric value
|
||||||
|
-- @field #ActionType Range Input action with a numeric value between 0 and 1 (inclusive)
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Values that can be used in registerAction
|
||||||
|
-- @field [parent=#input] #ACTION_TYPE ACTION_TYPE
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @type ActionInfo
|
||||||
|
-- @field [parent=#Actioninfo] #string key
|
||||||
|
-- @field [parent=#Actioninfo] #ActionType type
|
||||||
|
-- @field [parent=#Actioninfo] #string l10n Localization context containing the name and description keys
|
||||||
|
-- @field [parent=#Actioninfo] #string name Localization key of the action's name
|
||||||
|
-- @field [parent=#Actioninfo] #string description Localization key of the action's description
|
||||||
|
-- @field [parent=#Actioninfo] defaultValue initial value of the action
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Map of all currently registered actions
|
||||||
|
-- @field [parent=#input] #map<#string,#ActionInfo> actions
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Registers a new input action. The key must be unique
|
||||||
|
-- @function [parent=#input] registerAction
|
||||||
|
-- @param #ActionInfo info
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Provides a function computing the value of given input action.
|
||||||
|
-- The callback is called once a frame, after the values of dependency actions are resolved.
|
||||||
|
-- Throws an error if a cyclic action dependency is detected.
|
||||||
|
-- @function [parent=#input] bindAction
|
||||||
|
-- @param #string key
|
||||||
|
-- @param openmw.async#Callback callback returning the new value of the action, and taking as arguments:
|
||||||
|
-- frame time in seconds,
|
||||||
|
-- value of the function,
|
||||||
|
-- value of the first dependency action,
|
||||||
|
-- ...
|
||||||
|
-- @param #list<#string> dependencies
|
||||||
|
-- @usage
|
||||||
|
-- input.bindAction('Activate', async:callback(function(dt, use, sneak, run)
|
||||||
|
-- -- while sneaking, only activate things while holding the run binding
|
||||||
|
-- return use and (run or not sneak)
|
||||||
|
-- end), { 'Sneak', 'Run' })
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Registers a function to be called whenever the action's value changes
|
||||||
|
-- @function [parent=#input] registerActionHandler
|
||||||
|
-- @param #string key
|
||||||
|
-- @param openmw.async#Callback callback takes the new action value as the only argument
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Returns the value of a Boolean action
|
||||||
|
-- @function [parent=#input] getBooleanActionValue
|
||||||
|
-- @param #string key
|
||||||
|
-- @return #boolean
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Returns the value of a Number action
|
||||||
|
-- @function [parent=#input] getNumberActionValue
|
||||||
|
-- @param #string key
|
||||||
|
-- @return #number
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Returns the value of a Range action
|
||||||
|
-- @function [parent=#input] getRangeActionValue
|
||||||
|
-- @param #string key
|
||||||
|
-- @return #number
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @type TriggerInfo
|
||||||
|
-- @field [parent=#Actioninfo] #string key
|
||||||
|
-- @field [parent=#Actioninfo] #string l10n Localization context containing the name and description keys
|
||||||
|
-- @field [parent=#Actioninfo] #string name Localization key of the trigger's name
|
||||||
|
-- @field [parent=#Actioninfo] #string description Localization key of the trigger's description
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Map of all currently registered triggers
|
||||||
|
-- @field [parent=#input] #map<#string,#TriggerInfo> triggers
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Registers a new input trigger. The key must be unique
|
||||||
|
-- @function [parent=#input] registerTrigger
|
||||||
|
-- @param #TriggerInfo info
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Registers a function to be called whenever the trigger activates
|
||||||
|
-- @function [parent=#input] registerTriggerHandler
|
||||||
|
-- @param #string key
|
||||||
|
-- @param openmw.async#Callback callback takes the new action value as the only argument
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Activates the trigger with the given key
|
||||||
|
-- @function [parent=#input] activateTrigger
|
||||||
|
-- @param #string key
|
||||||
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
Loading…
Reference in a new issue