From 1869aeae5c470c05adcbc65644181249650d6fec Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 5 Dec 2022 23:31:23 +0100 Subject: [PATCH] Move some of player controls logic from C++ to Lua --- apps/launcher/advancedpage.cpp | 2 - apps/openmw/mwbase/inputmanager.hpp | 1 - apps/openmw/mwbase/luamanager.hpp | 1 + apps/openmw/mwbase/windowmanager.hpp | 1 - apps/openmw/mwgui/windowmanagerimp.cpp | 29 ++- apps/openmw/mwgui/windowmanagerimp.hpp | 1 - apps/openmw/mwinput/actionmanager.cpp | 197 +----------------- apps/openmw/mwinput/actionmanager.hpp | 13 +- apps/openmw/mwinput/bindingsmanager.cpp | 66 ++---- apps/openmw/mwinput/controllermanager.cpp | 77 +------ apps/openmw/mwinput/controllermanager.hpp | 8 +- apps/openmw/mwinput/controlswitch.cpp | 20 +- apps/openmw/mwinput/inputmanagerimp.cpp | 13 +- apps/openmw/mwinput/inputmanagerimp.hpp | 1 - apps/openmw/mwlua/localscripts.cpp | 1 + apps/openmw/mwlua/luabindings.cpp | 2 +- apps/openmw/mwlua/types/actor.cpp | 33 +++ apps/openmw/mwlua/types/npc.cpp | 11 + apps/openmw/mwmechanics/actors.cpp | 19 +- apps/openmw/mwmechanics/character.cpp | 7 - apps/openmw/mwworld/player.cpp | 65 ------ apps/openmw/mwworld/player.hpp | 15 -- apps/openmw/mwworld/worldimp.cpp | 2 +- docs/source/generate_luadoc.sh | 1 + docs/source/reference/lua-scripting/api.rst | 5 + .../lua-scripting/interface_controls.rst | 6 + .../reference/lua-scripting/overview.rst | 4 + .../reference/modding/settings/input.rst | 27 --- files/data/CMakeLists.txt | 2 + files/data/builtin.omwscripts | 1 + files/data/l10n/OMWControls/en.yaml | 16 ++ files/data/scripts/omw/camera/camera.lua | 16 +- files/data/scripts/omw/playercontrols.lua | 189 +++++++++++++++++ files/lua_api/openmw/self.lua | 1 + files/lua_api/openmw/types.lua | 13 ++ files/settings-default.cfg | 6 - files/ui/advancedpage.ui | 12 +- .../test_lua_api/builtin.omwscripts | 1 + 38 files changed, 367 insertions(+), 518 deletions(-) create mode 100644 docs/source/reference/lua-scripting/interface_controls.rst create mode 100644 files/data/l10n/OMWControls/en.yaml create mode 100644 files/data/scripts/omw/playercontrols.lua create mode 100644 scripts/data/integration_tests/test_lua_api/builtin.omwscripts diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 9293ffa307..bfd12d27c1 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -84,7 +84,6 @@ bool Launcher::AdvancedPage::loadSettings() { // Game mechanics { - loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); @@ -253,7 +252,6 @@ void Launcher::AdvancedPage::saveSettings() { // Game mechanics { - saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input"); saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index 0e31f4f370..f52f9ea454 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -46,7 +46,6 @@ namespace MWBase virtual void setDragDrop(bool dragDrop) = 0; virtual void setGamepadGuiCursorEnabled(bool enabled) = 0; - virtual void setAttemptJump(bool jumping) = 0; virtual void toggleControlSwitch(std::string_view sw, bool value) = 0; virtual bool getControlSwitch(std::string_view sw) = 0; diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 6885e93c19..20f9f1a2be 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -70,6 +70,7 @@ namespace MWBase bool mJump = false; bool mRun = false; + bool mSneak = false; float mMovement = 0; float mSideMovement = 0; float mPitchChange = 0; diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index e80f3c9d31..a05924d554 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -155,7 +155,6 @@ namespace MWBase virtual MWGui::CountDialog* getCountDialog() = 0; virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0; - virtual const std::vector>& getActiveMessageBoxes() const = 0; virtual MWGui::PostProcessorHud* getPostProcessorHud() = 0; /// Make the player use an item, while updating GUI state accordingly diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 4bbd729f47..e717fa1b3e 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -53,6 +53,7 @@ #include #include "../mwbase/inputmanager.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/world.hpp" @@ -794,11 +795,6 @@ namespace MWGui mMessageBoxManager->removeStaticMessageBox(); } - const std::vector>& WindowManager::getActiveMessageBoxes() const - { - return mMessageBoxManager->getActiveMessageBoxes(); - } - int WindowManager::readPressedButton() { return mMessageBoxManager->readPressedButton(); @@ -904,7 +900,8 @@ namespace MWGui // We should display message about crime only once per frame, even if there are several crimes. // Otherwise we will get message spam when stealing several items via Take All button. const MWWorld::Ptr player = MWMechanics::getPlayer(); - int currentBounty = player.getClass().getNpcStats(player).getBounty(); + const MWWorld::Class& playerCls = player.getClass(); + int currentBounty = playerCls.getNpcStats(player).getBounty(); if (currentBounty != mPlayerBounty) { if (mPlayerBounty >= 0 && currentBounty > mPlayerBounty) @@ -913,6 +910,26 @@ namespace MWGui mPlayerBounty = currentBounty; } + MWBase::LuaManager::ActorControls* playerControls + = MWBase::Environment::get().getLuaManager()->getActorControls(player); + bool triedToMove = playerControls + && (playerControls->mMovement != 0 || playerControls->mSideMovement != 0 || playerControls->mJump); + if (triedToMove && playerCls.getEncumbrance(player) > playerCls.getCapacity(player)) + { + const auto& msgboxs = mMessageBoxManager->getActiveMessageBoxes(); + auto it + = std::find_if(msgboxs.begin(), msgboxs.end(), [](const std::unique_ptr& msgbox) { + return (msgbox->getMessage() == "#{sNotifyMessage59}"); + }); + + // if an overencumbered messagebox is already present, reset its expiry timer, + // otherwise create a new one. + if (it != msgboxs.end()) + (*it)->mCurrentTime = 0; + else + messageBox("#{sNotifyMessage59}"); + } + mDragAndDrop->onFrame(); mHud->onFrame(frameDuration); diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 3b31397dde..dd02ba2169 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -179,7 +179,6 @@ namespace MWGui MWGui::CountDialog* getCountDialog() override; MWGui::ConfirmationDialog* getConfirmationDialog() override; MWGui::TradeWindow* getTradeWindow() override; - const std::vector>& getActiveMessageBoxes() const override; MWGui::PostProcessorHud* getPostProcessorHud() override; /// Make the player use an item, while updating GUI state accordingly diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index 23f4466c3a..776feae71c 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -36,99 +36,12 @@ namespace MWInput , mViewer(viewer) , mScreenCaptureHandler(screenCaptureHandler) , mScreenCaptureOperation(screenCaptureOperation) - , mAlwaysRunActive(Settings::Manager::getBool("always run", "Input")) - , mAttemptJump(false) , mTimeIdle(0.f) { } - void ActionManager::update(float dt, bool triedToMove) + void ActionManager::update(float dt) { - // Disable movement in Gui mode - if (MWBase::Environment::get().getWindowManager()->isGuiMode() - || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) - { - mAttemptJump = false; - return; - } - - // Configure player movement according to keyboard input. Actual movement will - // be done in the physics system. - if (MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) - { - bool alwaysRunAllowed = false; - - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - - if (mBindingsManager->actionIsActive(A_MoveLeft) != mBindingsManager->actionIsActive(A_MoveRight)) - { - alwaysRunAllowed = true; - triedToMove = true; - player.setLeftRight(mBindingsManager->actionIsActive(A_MoveRight) ? 1 : -1); - } - - if (mBindingsManager->actionIsActive(A_MoveForward) != mBindingsManager->actionIsActive(A_MoveBackward)) - { - alwaysRunAllowed = true; - triedToMove = true; - player.setAutoMove(false); - player.setForwardBackward(mBindingsManager->actionIsActive(A_MoveForward) ? 1 : -1); - } - - if (player.getAutoMove()) - { - alwaysRunAllowed = true; - triedToMove = true; - player.setForwardBackward(1); - } - - if (mAttemptJump && MWBase::Environment::get().getInputManager()->getControlSwitch("playerjumping")) - { - player.setUpDown(1); - triedToMove = true; - } - - // if player tried to start moving, but can't (due to being overencumbered), display a notification. - if (triedToMove) - { - MWWorld::Ptr playerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (playerPtr.getClass().getEncumbrance(playerPtr) > playerPtr.getClass().getCapacity(playerPtr)) - { - player.setAutoMove(false); - const auto& msgboxs = MWBase::Environment::get().getWindowManager()->getActiveMessageBoxes(); - auto it = std::find_if( - msgboxs.begin(), msgboxs.end(), [](const std::unique_ptr& msgbox) { - return (msgbox->getMessage() == "#{sNotifyMessage59}"); - }); - - // if an overencumbered messagebox is already present, reset its expiry timer, otherwise create new - // one. - if (it != msgboxs.end()) - (*it)->mCurrentTime = 0; - else - MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage59}"); - } - } - - if (triedToMove) - MWBase::Environment::get().getInputManager()->resetIdleTime(); - - static const bool isToggleSneak = Settings::Manager::getBool("toggle sneak", "Input"); - if (!isToggleSneak) - { - if (!MWBase::Environment::get().getInputManager()->joystickLastUsed()) - player.setSneak(mBindingsManager->actionIsActive(A_Sneak)); - } - - float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight); - float yAxis = mBindingsManager->getActionValue(A_MoveForwardBackward); - bool isRunning = osg::Vec2f(xAxis * 2 - 1, yAxis * 2 - 1).length2() > 0.25f; - if ((mAlwaysRunActive && alwaysRunAllowed) || isRunning) - player.setRunState(!mBindingsManager->actionIsActive(A_Run)); - else - player.setRunState(mBindingsManager->actionIsActive(A_Run)); - } - if (mBindingsManager->actionIsActive(A_MoveForward) || mBindingsManager->actionIsActive(A_MoveBackward) || mBindingsManager->actionIsActive(A_MoveLeft) || mBindingsManager->actionIsActive(A_MoveRight) || mBindingsManager->actionIsActive(A_Jump) || mBindingsManager->actionIsActive(A_Sneak) @@ -139,8 +52,6 @@ namespace MWInput } else mTimeIdle += dt; - - mAttemptJump = false; } void ActionManager::resetIdleTime() @@ -181,21 +92,9 @@ namespace MWInput case A_Journal: toggleJournal(); break; - case A_AutoMove: - toggleAutoMove(); - break; - case A_AlwaysRun: - toggleWalking(); - break; - case A_ToggleWeapon: - toggleWeapon(); - break; case A_Rest: rest(); break; - case A_ToggleSpell: - toggleSpell(); - break; case A_QuickKey1: quickKey(1); break; @@ -260,13 +159,6 @@ namespace MWInput if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Inventory)) MWBase::Environment::get().getWindowManager()->cycleWeapon(true); break; - case A_Sneak: - static const bool isToggleSneak = Settings::Manager::getBool("toggle sneak", "Input"); - if (isToggleSneak) - { - toggleSneaking(); - } - break; } } @@ -334,36 +226,6 @@ namespace MWInput } } - void ActionManager::toggleSpell() - { - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) - return; - - // Not allowed before the magic window is accessible - if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playermagic") - || !MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) - return; - - if (!checkAllowedToUseItems()) - return; - - // Not allowed if no spell selected - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - MWWorld::InventoryStore& inventory = player.getPlayer().getClass().getInventoryStore(player.getPlayer()); - if (MWBase::Environment::get().getWindowManager()->getSelectedSpell().empty() - && inventory.getSelectedEnchantItem() == inventory.end()) - return; - - if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player.getPlayer())) - return; - - MWMechanics::DrawState state = player.getDrawState(); - if (state == MWMechanics::DrawState::Weapon || state == MWMechanics::DrawState::Nothing) - player.setDrawState(MWMechanics::DrawState::Spell); - else - player.setDrawState(MWMechanics::DrawState::Nothing); - } - void ActionManager::quickLoad() { if (!MyGUI::InputManager::getInstance().isModalAny()) @@ -376,32 +238,6 @@ namespace MWInput MWBase::Environment::get().getStateManager()->quickSave(); } - void ActionManager::toggleWeapon() - { - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) - return; - - // Not allowed before the inventory window is accessible - if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playerfighting") - || !MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) - return; - - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - // We want to interrupt animation only if attack is preparing, but still is not triggered - // Otherwise we will get a "speedshooting" exploit, when player can skip reload animation by hitting "Toggle - // Weapon" key twice - if (MWBase::Environment::get().getMechanicsManager()->isAttackPreparing(player.getPlayer())) - player.setAttackingOrSpell(false); - else if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(player.getPlayer())) - return; - - MWMechanics::DrawState state = player.getDrawState(); - if (state == MWMechanics::DrawState::Spell || state == MWMechanics::DrawState::Nothing) - player.setDrawState(MWMechanics::DrawState::Weapon); - else - player.setDrawState(MWMechanics::DrawState::Nothing); - } - void ActionManager::rest() { if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) @@ -513,43 +349,12 @@ namespace MWInput } } - void ActionManager::toggleAutoMove() - { - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) - return; - - if (MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) - { - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - player.setAutoMove(!player.getAutoMove()); - } - } - - void ActionManager::toggleWalking() - { - if (MWBase::Environment::get().getWindowManager()->isGuiMode() || SDL_IsTextInputActive()) - return; - mAlwaysRunActive = !mAlwaysRunActive; - - Settings::Manager::setBool("always run", "Input", mAlwaysRunActive); - } - bool ActionManager::isSneaking() const { const MWBase::Environment& env = MWBase::Environment::get(); return env.getMechanicsManager()->isSneaking(env.getWorld()->getPlayer().getPlayer()); } - void ActionManager::toggleSneaking() - { - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) - return; - if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) - return; - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - player.setSneak(!isSneaking()); - } - void ActionManager::handleGuiArrowKey(int action) { bool joystickUsed = MWBase::Environment::get().getInputManager()->joystickLastUsed(); diff --git a/apps/openmw/mwinput/actionmanager.hpp b/apps/openmw/mwinput/actionmanager.hpp index bee3236e09..0979e9ad78 100644 --- a/apps/openmw/mwinput/actionmanager.hpp +++ b/apps/openmw/mwinput/actionmanager.hpp @@ -21,23 +21,18 @@ namespace MWInput osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation, osg::ref_ptr viewer, osg::ref_ptr screenCaptureHandler); - void update(float dt, bool triedToMove); + void update(float dt); void executeAction(int action); bool checkAllowedToUseItems() const; void toggleMainMenu(); - void toggleSpell(); - void toggleWeapon(); void toggleInventory(); void toggleConsole(); void screenshot(); void toggleJournal(); void activate(); - void toggleWalking(); - void toggleSneaking(); - void toggleAutoMove(); void rest(); void quickLoad(); void quickSave(); @@ -48,11 +43,8 @@ namespace MWInput void resetIdleTime(); float getIdleTime() const { return mTimeIdle; } - bool isAlwaysRunActive() const { return mAlwaysRunActive; } bool isSneaking() const; - void setAttemptJump(bool enabled) { mAttemptJump = enabled; } - private: void handleGuiArrowKey(int action); @@ -61,9 +53,6 @@ namespace MWInput osg::ref_ptr mScreenCaptureHandler; osgViewer::ScreenCaptureHandler::CaptureOperation* mScreenCaptureOperation; - bool mAlwaysRunActive; - bool mAttemptJump; - float mTimeIdle; }; } diff --git a/apps/openmw/mwinput/bindingsmanager.cpp b/apps/openmw/mwinput/bindingsmanager.cpp index 609be27ddf..a010b4e745 100644 --- a/apps/openmw/mwinput/bindingsmanager.cpp +++ b/apps/openmw/mwinput/bindingsmanager.cpp @@ -681,65 +681,25 @@ namespace MWInput void BindingsManager::actionValueChanged(int action, float currentValue, float previousValue) { - MWBase::Environment::get().getInputManager()->resetIdleTime(); + auto manager = MWBase::Environment::get().getInputManager(); + manager->resetIdleTime(); if (mDragDrop && action != A_GameMenu && action != A_Inventory) return; - if ((previousValue == 1 || previousValue == 0) && (currentValue == 1 || currentValue == 0)) + if (manager->joystickLastUsed() && manager->getControlSwitch("playercontrols")) { - // Is a normal button press, so don't change it at all - } - // Otherwise only trigger button presses as they go through specific points - else if (previousValue >= 0.8 && currentValue < 0.8) - { - currentValue = 0.0; - previousValue = 1.0; - } - else if (previousValue <= 0.6 && currentValue > 0.6) - { - currentValue = 1.0; - previousValue = 0.0; - } - else - { - // If it's not switching between those values, ignore the channel change. - return; + if (action == A_Use && actionIsActive(A_ToggleWeapon)) + action = A_CycleWeaponRight; + else if (action == A_Use && actionIsActive(A_ToggleSpell)) + action = A_CycleSpellRight; + else if (action == A_Jump && actionIsActive(A_ToggleWeapon)) + action = A_CycleWeaponLeft; + else if (action == A_Jump && actionIsActive(A_ToggleSpell)) + action = A_CycleSpellLeft; } - if (MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) - { - bool joystickUsed = MWBase::Environment::get().getInputManager()->joystickLastUsed(); - if (action == A_Use) - { - if (joystickUsed && currentValue == 1.0 && actionIsActive(A_ToggleWeapon)) - action = A_CycleWeaponRight; - - else if (joystickUsed && currentValue == 1.0 && actionIsActive(A_ToggleSpell)) - action = A_CycleSpellRight; - - else - { - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - MWMechanics::DrawState state = player.getDrawState(); - player.setAttackingOrSpell(currentValue != 0 && state != MWMechanics::DrawState::Nothing); - } - } - else if (action == A_Jump) - { - if (joystickUsed && currentValue == 1.0 && actionIsActive(A_ToggleWeapon)) - action = A_CycleWeaponLeft; - - else if (joystickUsed && currentValue == 1.0 && actionIsActive(A_ToggleSpell)) - action = A_CycleSpellLeft; - - else - MWBase::Environment::get().getInputManager()->setAttemptJump( - currentValue == 1.0 && previousValue == 0.0); - } - } - - if (currentValue == 1) - MWBase::Environment::get().getInputManager()->executeAction(action); + if (previousValue <= 0.6 && currentValue > 0.6) + manager->executeAction(action); } } diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 919a193d9f..c42c7aa649 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -25,20 +25,16 @@ namespace MWInput { - ControllerManager::ControllerManager(BindingsManager* bindingsManager, ActionManager* actionManager, - MouseManager* mouseManager, const std::filesystem::path& userControllerBindingsFile, - const std::filesystem::path& controllerBindingsFile) + ControllerManager::ControllerManager(BindingsManager* bindingsManager, MouseManager* mouseManager, + const std::filesystem::path& userControllerBindingsFile, const std::filesystem::path& controllerBindingsFile) : mBindingsManager(bindingsManager) - , mActionManager(actionManager) , mMouseManager(mouseManager) , mJoystickEnabled(Settings::Manager::getBool("enable controller", "Input")) , mGyroAvailable(false) , mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input")) - , mSneakToggleShortcutTimer(0.f) , mGamepadGuiCursorEnabled(true) , mGuiCursorEnabled(true) , mJoystickLastUsed(false) - , mSneakGamepadShortcut(false) { if (!controllerBindingsFile.empty()) { @@ -82,7 +78,7 @@ namespace MWInput } } - bool ControllerManager::update(float dt) + void ControllerManager::update(float dt) { if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled)) { @@ -108,77 +104,18 @@ namespace MWInput } } - // Disable movement in Gui mode - if (MWBase::Environment::get().getWindowManager()->isGuiMode() - || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) - { - return false; - } - - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - bool triedToMove = false; - - // Configure player movement according to controller input. Actual movement will - // be done in the physics system. - if (MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) + if (!MWBase::Environment::get().getWindowManager()->isGuiMode() + && MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_Running + && MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) { float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight); float yAxis = mBindingsManager->getActionValue(A_MoveForwardBackward); - if (xAxis != 0.5) - { - triedToMove = true; - player.setLeftRight((xAxis - 0.5f) * 2); - } - - if (yAxis != 0.5) - { - triedToMove = true; - player.setAutoMove(false); - player.setForwardBackward((0.5f - yAxis) * 2); - } - - if (triedToMove) + if (xAxis != 0.5 || yAxis != 0.5) { mJoystickLastUsed = true; MWBase::Environment::get().getInputManager()->resetIdleTime(); } - - static const bool isToggleSneak = Settings::Manager::getBool("toggle sneak", "Input"); - if (!isToggleSneak) - { - if (mJoystickLastUsed) - { - if (mBindingsManager->actionIsActive(A_Sneak)) - { - if (mSneakToggleShortcutTimer) // New Sneak Button Press - { - if (mSneakToggleShortcutTimer <= 0.3f) - { - mSneakGamepadShortcut = true; - mActionManager->toggleSneaking(); - } - else - mSneakGamepadShortcut = false; - } - - if (!mActionManager->isSneaking()) - mActionManager->toggleSneaking(); - mSneakToggleShortcutTimer = 0.f; - } - else - { - if (!mSneakGamepadShortcut && mActionManager->isSneaking()) - mActionManager->toggleSneaking(); - if (mSneakToggleShortcutTimer <= 0.3f) - mSneakToggleShortcutTimer += dt; - } - } - else - player.setSneak(mBindingsManager->actionIsActive(A_Sneak)); - } } - - return triedToMove; } void ControllerManager::buttonPressed(int deviceID, const SDL_ControllerButtonEvent& arg) diff --git a/apps/openmw/mwinput/controllermanager.hpp b/apps/openmw/mwinput/controllermanager.hpp index cbd279204f..de959fc8cb 100644 --- a/apps/openmw/mwinput/controllermanager.hpp +++ b/apps/openmw/mwinput/controllermanager.hpp @@ -9,20 +9,19 @@ namespace MWInput { - class ActionManager; class BindingsManager; class MouseManager; class ControllerManager : public SDLUtil::ControllerListener { public: - ControllerManager(BindingsManager* bindingsManager, ActionManager* actionManager, MouseManager* mouseManager, + ControllerManager(BindingsManager* bindingsManager, MouseManager* mouseManager, const std::filesystem::path& userControllerBindingsFile, const std::filesystem::path& controllerBindingsFile); virtual ~ControllerManager() = default; - bool update(float dt); + void update(float dt); void buttonPressed(int deviceID, const SDL_ControllerButtonEvent& arg) override; void buttonReleased(int deviceID, const SDL_ControllerButtonEvent& arg) override; @@ -58,17 +57,14 @@ namespace MWInput void enableGyroSensor(); BindingsManager* mBindingsManager; - ActionManager* mActionManager; MouseManager* mMouseManager; bool mJoystickEnabled; bool mGyroAvailable; float mGamepadCursorSpeed; - float mSneakToggleShortcutTimer; bool mGamepadGuiCursorEnabled; bool mGuiCursorEnabled; bool mJoystickLastUsed; - bool mSneakGamepadShortcut; }; } #endif diff --git a/apps/openmw/mwinput/controlswitch.cpp b/apps/openmw/mwinput/controlswitch.cpp index 74fa101e4e..bf9842ae30 100644 --- a/apps/openmw/mwinput/controlswitch.cpp +++ b/apps/openmw/mwinput/controlswitch.cpp @@ -39,24 +39,10 @@ namespace MWInput void ControlSwitch::set(std::string_view key, bool value) { - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - - /// \note 7 switches at all, if-else is relevant - if (key == "playercontrols" && !value) + if (key == "playerlooking" && !value) { - player.setLeftRight(0); - player.setForwardBackward(0); - player.setAutoMove(false); - player.setUpDown(0); - } - else if (key == "playerjumping" && !value) - { - /// \fixme maybe crouching at this time - player.setUpDown(0); - } - else if (key == "playerlooking" && !value) - { - MWBase::Environment::get().getWorld()->rotateObject(player.getPlayer(), osg::Vec3f()); + auto world = MWBase::Environment::get().getWorld(); + world->rotateObject(world->getPlayerPtr(), osg::Vec3f()); } auto it = mSwitches.find(key); if (it == mSwitches.end()) diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index e31c0f1318..560bed8548 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -37,8 +37,8 @@ namespace MWInput mBindingsManager.get(), screenCaptureOperation, viewer, screenCaptureHandler)) , mKeyboardManager(std::make_unique(mBindingsManager.get())) , mMouseManager(std::make_unique(mBindingsManager.get(), mInputWrapper.get(), window)) - , mControllerManager(std::make_unique(mBindingsManager.get(), mActionManager.get(), - mMouseManager.get(), userControllerBindingsFile, controllerBindingsFile)) + , mControllerManager(std::make_unique( + mBindingsManager.get(), mMouseManager.get(), userControllerBindingsFile, controllerBindingsFile)) , mSensorManager(std::make_unique()) , mGyroManager(std::make_unique()) { @@ -57,11 +57,6 @@ namespace MWInput InputManager::~InputManager() {} - void InputManager::setAttemptJump(bool jumping) - { - mActionManager->setAttemptJump(jumping); - } - void InputManager::update(float dt, bool disableControls, bool disableEvents) { mControlsDisabled = disableControls; @@ -79,10 +74,10 @@ namespace MWInput mMouseManager->updateCursorMode(); - bool controllerMove = mControllerManager->update(dt); + mControllerManager->update(dt); mMouseManager->update(dt); mSensorManager->update(dt); - mActionManager->update(dt, controllerMove); + mActionManager->update(dt); if (mGyroManager->isEnabled()) { diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index f5690f277e..c5de579961 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -68,7 +68,6 @@ namespace MWInput void setDragDrop(bool dragDrop) override; void setGamepadGuiCursorEnabled(bool enabled) override; - void setAttemptJump(bool jumping) override; void toggleControlSwitch(std::string_view sw, bool value) override; bool getControlSwitch(std::string_view sw) override; diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index eb93037444..1c1bab4edf 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -48,6 +48,7 @@ namespace MWLua controls["pitchChange"] = CONTROL(float, mPitchChange); controls["yawChange"] = CONTROL(float, mYawChange); controls["run"] = CONTROL(bool, mRun); + controls["sneak"] = CONTROL(bool, mSneak); controls["jump"] = CONTROL(bool, mJump); controls["use"] = CONTROL(int, mUse); #undef CONTROL diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 452caea9b8..0e96db8b90 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -48,7 +48,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 30; + api["API_REVISION"] = 31; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); MWBase::Environment::get().getStateManager()->requestQuit(); diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 395596dd3d..de1f489748 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -150,6 +151,38 @@ namespace MWLua else throw std::runtime_error("Actor expected"); }; + actor["setStance"] = [](const SelfObject& self, int stance) { + const MWWorld::Class& cls = self.ptr().getClass(); + if (!cls.isActor()) + throw std::runtime_error("Actor expected"); + auto& stats = cls.getCreatureStats(self.ptr()); + if (stance != static_cast(MWMechanics::DrawState::Nothing) + && stance != static_cast(MWMechanics::DrawState::Weapon) + && stance != static_cast(MWMechanics::DrawState::Spell)) + { + throw std::runtime_error("Incorrect stance"); + } + MWMechanics::DrawState newDrawState = static_cast(stance); + if (stats.getDrawState() == newDrawState) + return; + if (newDrawState == MWMechanics::DrawState::Spell && stats.getSpells().getSelectedSpell().empty()) + { + if (!cls.hasInventoryStore(self.ptr())) + return; // No selected spell and no items; can't use magic stance. + MWWorld::InventoryStore& store = cls.getInventoryStore(self.ptr()); + if (store.getSelectedEnchantItem() == store.end()) + return; // No selected spell and no selected enchanted item; can't use magic stance. + } + MWBase::MechanicsManager* mechanics = MWBase::Environment::get().getMechanicsManager(); + // We want to interrupt animation only if attack is preparing, but still is not triggered. + // Otherwise we will get a "speedshooting" exploit, when player can skip reload animation by hitting "Toggle + // Weapon" key twice. + if (mechanics->isAttackPreparing(self.ptr())) + stats.setAttackingOrSpell(false); // interrupt attack + else if (mechanics->isAttackingOrSpell(self.ptr())) + return; // can't be interrupted; ignore setStance + stats.setDrawState(newDrawState); + }; actor["canMove"] = [](const Object& o) { const MWWorld::Class& cls = o.ptr().getClass(); diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index e25f02b3cb..6105c2dc96 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -5,6 +5,8 @@ #include #include +#include +#include #include #include "../stats.hpp" @@ -43,5 +45,14 @@ namespace MWLua = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHair.getRefIdString(); }); record["head"] = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHead.getRefIdString(); }); + + // This function is game-specific, in future we should replace it with something more universal. + npc["isWerewolf"] = [](const Object& o) { + const MWWorld::Class& cls = o.ptr().getClass(); + if (cls.isNpc()) + return cls.getNpcStats(o.ptr()).isWerewolf(); + else + throw std::runtime_error("NPC or Player expected"); + }; } } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 5484d068af..a0f0c9da9f 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -352,6 +352,7 @@ namespace MWMechanics const float rotationZ = mov.mRotation[2]; const bool jump = mov.mPosition[2] == 1; const bool runFlag = stats.getMovementFlag(MWMechanics::CreatureStats::Flag_Run); + const bool sneakFlag = stats.getMovementFlag(MWMechanics::CreatureStats::Flag_Sneak); const bool attackingOrSpell = stats.getAttackingOrSpell(); if (controls.mChanged) { @@ -363,16 +364,24 @@ namespace MWMechanics mov.mRotation[2] = controls.mYawChange; mov.mSpeedFactor = osg::Vec2(controls.mMovement, controls.mSideMovement).length(); stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, controls.mRun); + stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Sneak, controls.mSneak); stats.setAttackingOrSpell((controls.mUse & 1) == 1); controls.mChanged = false; } - controls.mSideMovement = movement.x(); - controls.mMovement = movement.y(); + // For the player we don't need to copy these values to Lua because mwinput doesn't change them. + // All handling of these player controls was moved from C++ to a built-in Lua script. + if (!isPlayer) + { + controls.mSideMovement = movement.x(); + controls.mMovement = movement.y(); + controls.mJump = jump; + controls.mRun = runFlag; + controls.mSneak = sneakFlag; + controls.mUse = attackingOrSpell ? controls.mUse | 1 : controls.mUse & ~1; + } + // For the player these controls are still handled by mwinput, so we need to update the values. controls.mPitchChange = rotationX; controls.mYawChange = rotationZ; - controls.mJump = jump; - controls.mRun = runFlag; - controls.mUse = attackingOrSpell ? controls.mUse | 1 : controls.mUse & ~1; } } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c8d96d25f3..21e30ec8ac 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1907,13 +1907,6 @@ namespace MWMechanics movementSettings.mSpeedFactor = std::min(vec.length(), 1.f); vec.normalize(); - // TODO: Move this check to mwinput. - // Joystick analogue movement. - // Due to the half way split between walking/running, we multiply speed by 2 while walking, unless a - // keyboard was used. - if (isPlayer && !isrunning && !sneak && !flying && movementSettings.mSpeedFactor <= 0.5f) - movementSettings.mSpeedFactor *= 2.f; - static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game"); if (smoothMovement) { diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 1f4aca0cf0..3a6417cde0 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -40,8 +40,6 @@ namespace MWWorld , mLastKnownExteriorPosition(0, 0, 0) , mMarkedPosition(ESM::Position()) , mMarkedCell(nullptr) - , mAutoMove(false) - , mForwardBackward(0) , mTeleported(false) , mCurrentCrimeId(-1) , mPaidCrimeId(-1) @@ -163,61 +161,6 @@ namespace MWWorld ptr.getClass().getNpcStats(ptr).setDrawState(state); } - bool Player::getAutoMove() const - { - return mAutoMove; - } - - void Player::setAutoMove(bool enable) - { - MWWorld::Ptr ptr = getPlayer(); - - mAutoMove = enable; - - int value = mForwardBackward; - - if (mAutoMove) - value = 1; - - ptr.getClass().getMovementSettings(ptr).mPosition[1] = value; - } - - void Player::setLeftRight(float value) - { - MWWorld::Ptr ptr = getPlayer(); - ptr.getClass().getMovementSettings(ptr).mPosition[0] = value; - } - - void Player::setForwardBackward(float value) - { - MWWorld::Ptr ptr = getPlayer(); - - mForwardBackward = value; - - if (mAutoMove) - value = 1; - - ptr.getClass().getMovementSettings(ptr).mPosition[1] = value; - } - - void Player::setUpDown(int value) - { - MWWorld::Ptr ptr = getPlayer(); - ptr.getClass().getMovementSettings(ptr).mPosition[2] = static_cast(value); - } - - void Player::setRunState(bool run) - { - MWWorld::Ptr ptr = getPlayer(); - ptr.getClass().getCreatureStats(ptr).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, run); - } - - void Player::setSneak(bool sneak) - { - MWWorld::Ptr ptr = getPlayer(); - ptr.getClass().getCreatureStats(ptr).setMovementFlag(MWMechanics::CreatureStats::Flag_Sneak, sneak); - } - void Player::yaw(float yaw) { MWWorld::Ptr ptr = getPlayer(); @@ -272,11 +215,6 @@ namespace MWWorld mTeleported = teleported; } - void Player::setAttackingOrSpell(bool attackingOrSpell) - { - getPlayer().getClass().getCreatureStats(getPlayer()).setAttackingOrSpell(attackingOrSpell); - } - void Player::setJumping(bool jumping) { mJumping = jumping; @@ -315,8 +253,6 @@ namespace MWWorld mCellStore = nullptr; mSign = ESM::RefId::sEmpty; mMarkedCell = nullptr; - mAutoMove = false; - mForwardBackward = 0; mTeleported = false; mJumping = false; mCurrentCrimeId = -1; @@ -475,7 +411,6 @@ namespace MWWorld mMarkedCell = nullptr; } - mForwardBackward = 0; mTeleported = false; mPreviousItems = player.mPreviousItems; diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index d701b9e359..913ff00b02 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -41,8 +41,6 @@ namespace MWWorld // If no position was marked, this is nullptr CellStore* mMarkedCell; - bool mAutoMove; - float mForwardBackward; bool mTeleported; int mCurrentCrimeId; // the id assigned witnesses @@ -90,17 +88,6 @@ namespace MWWorld /// Activate the object under the crosshair, if any void activate(); - bool getAutoMove() const; - void setAutoMove(bool enable); - - void setLeftRight(float value); - - void setForwardBackward(float value); - void setUpDown(int value); - - void setRunState(bool run); - void setSneak(bool sneak); - void yaw(float yaw); void pitch(float pitch); void roll(float roll); @@ -108,8 +95,6 @@ namespace MWWorld bool wasTeleported() const; void setTeleported(bool teleported); - void setAttackingOrSpell(bool attackingOrSpell); - void setJumping(bool jumping); bool getJumping() const; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 328610bc40..e645094630 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3683,7 +3683,7 @@ namespace MWWorld { if (MWBase::Environment::get().getMechanicsManager()->isAttackPreparing(player)) { - mPlayer->setAttackingOrSpell(false); + player.getClass().getCreatureStats(player).setAttackingOrSpell(false); } mPlayer->setDrawState(MWMechanics::DrawState::Nothing); diff --git a/docs/source/generate_luadoc.sh b/docs/source/generate_luadoc.sh index 164074d5ca..fc3d6dc999 100755 --- a/docs/source/generate_luadoc.sh +++ b/docs/source/generate_luadoc.sh @@ -26,6 +26,7 @@ $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw/*lua cd $FILES_DIR/data $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw_aux/*lua $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/ai.lua +$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/playercontrols.lua $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/camera/camera.lua $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/mwui/init.lua $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/settings/player.lua diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 23bf9cb3a2..e59b926d0f 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -29,6 +29,7 @@ Lua API reference openmw_aux_ui interface_ai interface_camera + interface_controls interface_mwui interface_settings iterables @@ -73,6 +74,10 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid - by player scripts - | Allows to alter behavior of the built-in camera script | without overriding the script completely. + * - :ref:`Controls ` + - by player scripts + - | Allows to alter behavior of the built-in script + | that handles player controls. * - :ref:`Settings ` - by player and global scripts - Save, display and track changes of setting values. diff --git a/docs/source/reference/lua-scripting/interface_controls.rst b/docs/source/reference/lua-scripting/interface_controls.rst new file mode 100644 index 0000000000..e458aca133 --- /dev/null +++ b/docs/source/reference/lua-scripting/interface_controls.rst @@ -0,0 +1,6 @@ +Interface Controls +================== + +.. raw:: html + :file: generated_html/scripts_omw_playercontrols.html + diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 4cceb26088..47008428ab 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -452,6 +452,10 @@ The order in which the scripts are started is important. So if one mod should ov - by player scripts - | Allows to alter behavior of the built-in camera script | without overriding the script completely. + * - :ref:`Controls ` + - by player scripts + - | Allows to alter behavior of the built-in script + | that handles player controls. * - :ref:`Settings ` - by player and global scripts - Save, display and track changes of setting values. diff --git a/docs/source/reference/modding/settings/input.rst b/docs/source/reference/modding/settings/input.rst index 7aedf1bc21..8d1bdee859 100644 --- a/docs/source/reference/modding/settings/input.rst +++ b/docs/source/reference/modding/settings/input.rst @@ -27,33 +27,6 @@ to prevent the mouse cursor from becoming unusable when the game pauses on a bre This setting can only be configured by editing the settings configuration file. -toggle sneak ------------- - -:Type: boolean -:Range: True/False -:Default: False - -This setting causes the behavior of the sneak key (bound to Ctrl by default) -to toggle sneaking on and off 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. - -This setting can be toggled in the launcher under "Advanced" -> "Game Mechanics" -> "Toggle sneak". - -always run ----------- - -:Type: boolean -:Range: True/False -:Default: False - -If this setting is true, the character is running by default, otherwise the character is walking by default. -The shift key will temporarily invert this setting, and the caps lock key will invert this setting while it's "locked". -This setting is updated every time you exit the game, -based on whether the caps lock key was on or off at the time you exited. - -This settings can be toggled in game by pressing the CapsLock key and exiting. - camera sensitivity ------------------ diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index 2630fbed84..ddc549e7e2 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -50,6 +50,7 @@ set(BUILTIN_DATA_FILES l10n/OMWCamera/ru.yaml l10n/OMWCamera/sv.yaml l10n/OMWCamera/fr.yaml + l10n/OMWControls/en.yaml l10n/PostProcessing/de.yaml l10n/PostProcessing/en.yaml l10n/PostProcessing/ru.yaml @@ -83,6 +84,7 @@ set(BUILTIN_DATA_FILES scripts/omw/console/player.lua scripts/omw/console/global.lua scripts/omw/console/local.lua + scripts/omw/playercontrols.lua scripts/omw/settings/player.lua scripts/omw/settings/global.lua scripts/omw/settings/common.lua diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index 8c88045ad7..a3eed5ea16 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -1,6 +1,7 @@ PLAYER: scripts/omw/mwui/init.lua GLOBAL: scripts/omw/settings/global.lua PLAYER: scripts/omw/settings/player.lua +PLAYER: scripts/omw/playercontrols.lua PLAYER: scripts/omw/camera/camera.lua NPC,CREATURE: scripts/omw/ai.lua PLAYER: scripts/omw/console/player.lua diff --git a/files/data/l10n/OMWControls/en.yaml b/files/data/l10n/OMWControls/en.yaml new file mode 100644 index 0000000000..de6edde19a --- /dev/null +++ b/files/data/l10n/OMWControls/en.yaml @@ -0,0 +1,16 @@ +ControlsPage: "OpenMW Controls" +ControlsPageDescription: "Additional settings related to player controls" + +MovementSettings: "Movement" + +alwaysRun: "Always run" +alwaysRunDescription: | + If this setting is true, the character is running by default, otherwise the character is walking by default. + The shift key will temporarily invert this setting, and the caps lock key will invert this setting while it's "locked". + +toggleSneak: "Toggle sneak" +toggleSneakDescription: | + This setting causes the behavior of the sneak key (bound to Ctrl by default) + to toggle sneaking on and off 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. + diff --git a/files/data/scripts/omw/camera/camera.lua b/files/data/scripts/omw/camera/camera.lua index 46fa732247..4737747e9d 100644 --- a/files/data/scripts/omw/camera/camera.lua +++ b/files/data/scripts/omw/camera/camera.lua @@ -84,11 +84,6 @@ local idleTimer = 0 local vanityDelay = core.getGMST('fVanityDelay') local function updateVanity(dt) - if input.isIdle() then - idleTimer = idleTimer + dt - else - idleTimer = 0 - end local vanityAllowed = input.getControlSwitch(input.CONTROL_SWITCH.VanityMode) if vanityAllowed and idleTimer > vanityDelay and camera.getMode() ~= MODE.Vanity then camera.setMode(MODE.Vanity) @@ -177,8 +172,19 @@ local function onUpdate(dt) pov_auto_switch.onUpdate(dt) end +local function updateIdleTimer(dt) + if not input.isIdle() then + idleTimer = 0 + 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 + else + idleTimer = idleTimer + dt + end +end + local function onFrame(dt) if core.isWorldPaused() then return end + updateIdleTimer(dt) local mode = camera.getMode() if mode == MODE.FirstPerson or mode == MODE.ThirdPerson then primaryMode = mode diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua new file mode 100644 index 0000000000..f2be8151d3 --- /dev/null +++ b/files/data/scripts/omw/playercontrols.lua @@ -0,0 +1,189 @@ +local core = require('openmw.core') +local input = require('openmw.input') +local self = require('openmw.self') +local util = require('openmw.util') +local ui = require('openmw.ui') +local Actor = require('openmw.types').Actor +local Player = require('openmw.types').Player + +local storage = require('openmw.storage') +local I = require('openmw.interfaces') + +local settingsGroup = 'SettingsOMWControls' + +local function boolSetting(key, default) + return { + key = key, + renderer = 'checkbox', + name = key, + description = key..'Description', + default = default, + } +end + +I.Settings.registerPage({ + key = 'OMWControls', + l10n = 'OMWControls', + name = 'ControlsPage', + description = 'ControlsPageDescription', +}) + +I.Settings.registerGroup({ + key = settingsGroup, + page = 'OMWControls', + l10n = 'OMWControls', + name = 'MovementSettings', + permanentStorage = true, + settings = { + boolSetting('alwaysRun', false), + boolSetting('toggleSneak', false), + }, +}) + +local settings = storage.playerSection(settingsGroup) + +local attemptJump = false +local startAttack = false +local autoMove = false +local movementControlsOverridden = false +local combatControlsOverridden = false + +local function processMovement() + local controllerMovement = -input.getAxisValue(input.CONTROLLER_AXIS.MoveForwardBackward) + local controllerSideMovement = input.getAxisValue(input.CONTROLLER_AXIS.MoveLeftRight) + if controllerMovement ~= 0 or controllerSideMovement ~= 0 then + -- controller movement + if util.vector2(controllerMovement, controllerSideMovement):length2() < 0.25 + and not self.controls.sneak and Actor.isOnGround(self) and not Actor.isSwimming(self) then + self.controls.run = false + self.controls.movement = controllerMovement * 2 + self.controls.sideMovement = controllerSideMovement * 2 + else + self.controls.run = true + self.controls.movement = controllerMovement + 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 + 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 input.getControlSwitch(input.CONTROL_SWITCH.Jumping) + if not settings:get('toggleSneak') then + self.controls.sneak = input.isActionPressed(input.ACTION.Sneak) + end +end + +local function processAttacking() + if startAttack then + self.controls.use = 1 + elseif Actor.stance(self) == Actor.STANCE.Spell then + self.controls.use = 0 + elseif input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight) < 0.6 + and not input.isActionPressed(input.ACTION.Use) then + -- 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. + self.controls.use = 0 + end +end + +local function onFrame(dt) + controlsAllowed = input.getControlSwitch(input.CONTROL_SWITCH.Controls) and not core.isWorldPaused() + 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 onInputAction(action) + if core.isWorldPaused() or not input.getControlSwitch(input.CONTROL_SWITCH.Controls) then + return + end + + if action == input.ACTION.Jump then + attemptJump = true + elseif action == input.ACTION.Use then + startAttack = true + elseif action == input.ACTION.AutoMove and not movementControlsOverridden then + autoMove = true + elseif action == input.ACTION.AlwaysRun and not movementControlsOverridden then + settings:set('alwaysRun', not settings:get('alwaysRun')) + elseif action == input.ACTION.Sneak and not movementControlsOverridden then + if settings:get('toggleSneak') then + self.controls.sneak = not self.controls.sneak + end + elseif action == input.ACTION.ToggleSpell and not combatControlsOverridden then + if Actor.stance(self) == Actor.STANCE.Spell then + Actor.setStance(self, Actor.STANCE.Nothing) + elseif input.getControlSwitch(input.CONTROL_SWITCH.Magic) then + if Player.isWerewolf(self) then + ui.showMessage(core.getGMST('sWerewolfRefusal')) + else + Actor.setStance(self, Actor.STANCE.Spell) + end + end + elseif action == input.ACTION.ToggleWeapon and not combatControlsOverridden then + if Actor.stance(self) == Actor.STANCE.Weapon then + Actor.setStance(self, Actor.STANCE.Nothing) + elseif input.getControlSwitch(input.CONTROL_SWITCH.Fighting) then + Actor.setStance(self, Actor.STANCE.Weapon) + end + end +end + +return { + engineHandlers = { + onFrame = onFrame, + onInputAction = onInputAction, + }, + interfaceName = 'Controls', + --- + -- @module Controls + -- @usage require('openmw.interfaces').Controls + interface = { + --- Interface version + -- @field [parent=#Controls] #number version + version = 0, + + --- When set to true then the movement controls including jump and sneak are not processed and can be handled by another script. + -- If movement should be dissallowed completely, consider to use `input.setControlSwitch` instead. + -- @function [parent=#Controls] overrideMovementControls + -- @param #boolean value + overrideMovementControls = function(v) movementControlsOverridden = v end, + + --- When set to true then the controls "attack", "toggle spell", "toggle weapon" are not processed and can be handled by another script. + -- If combat should be dissallowed completely, consider to use `input.setControlSwitch` instead. + -- @function [parent=#Controls] overrideCombatControls + -- @param #boolean value + overrideCombatControls = function(v) combatControlsOverridden = v end, + } +} + diff --git a/files/lua_api/openmw/self.lua b/files/lua_api/openmw/self.lua index 06be20ee64..02c19a1bcf 100644 --- a/files/lua_api/openmw/self.lua +++ b/files/lua_api/openmw/self.lua @@ -33,6 +33,7 @@ -- @field [parent=#ActorControls] #number yawChange Turn right (radians); if negative - turn left -- @field [parent=#ActorControls] #number pitchChange Look down (radians); if negative - look up -- @field [parent=#ActorControls] #boolean run true - run, false - walk +-- @field [parent=#ActorControls] #boolean sneak If true - sneak -- @field [parent=#ActorControls] #boolean jump If true - initiate a jump -- @field [parent=#ActorControls] #number use if 1 - activates the readied weapon/spell. For weapons, keeping at 1 will charge the attack until set to 0. diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 21ef8dd130..f43fbe1963 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -105,6 +105,13 @@ -- @param openmw.core#GameObject actor -- @return #number +--- +-- Sets the current stance (whether a weapon/spell is readied), see the list of @{#STANCE} values. +-- Can be used only in local scripts on self. +-- @function [parent=#Actor] setStance +-- @param openmw.core#GameObject actor +-- @param #number stance + --- -- Returns `true` if the item is equipped on the actor. -- @function [parent=#Actor] isEquipped @@ -477,6 +484,12 @@ -- @param openmw.core#GameObject object -- @return #boolean +--- +-- Whether the actor is in the werewolf form at the moment. +-- @function [parent=#Actor] isWerewolf +-- @param openmw.core#GameObject actor +-- @return #boolean + --- -- Returns the read-only @{#NpcRecord} of an NPC -- @function [parent=#NPC] record diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 33b1dd89d1..7c6c2e62fb 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -504,12 +504,6 @@ soft particles = false # Capture control of the cursor prevent movement outside the window. grab cursor = true -# Key controlling sneak toggles setting instead of being held down. -toggle sneak = false - -# Player is running by default. -always run = false - # Camera sensitivity when not in GUI mode. (>0.0, e.g. 0.1 to 5.0). camera sensitivity = 1.0 diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index 5766910f2e..9bab74ef76 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -63,16 +63,6 @@ - - - - <html><head/><body><p>This setting causes the behavior of the sneak key (bound to Ctrl by default) to toggle sneaking on and off 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. </p></body></html> - - - Toggle sneak - - - @@ -83,7 +73,7 @@ - + <html><head/><body><p>Make stealing items from NPCs that were knocked down possible during combat.</p></body></html> diff --git a/scripts/data/integration_tests/test_lua_api/builtin.omwscripts b/scripts/data/integration_tests/test_lua_api/builtin.omwscripts new file mode 100644 index 0000000000..8b7db327c5 --- /dev/null +++ b/scripts/data/integration_tests/test_lua_api/builtin.omwscripts @@ -0,0 +1 @@ +# It is an empty file that overrides builtin.omwscripts and disables builtin scripts