Merge branch 'playercontrols' into 'master'

Move some of player controls logic from C++ to Lua

See merge request OpenMW/openmw!2541
iwyu_full
psi29a 2 years ago
commit d5f5e53714

@ -84,7 +84,6 @@ bool Launcher::AdvancedPage::loadSettings()
{ {
// Game mechanics // Game mechanics
{ {
loadSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); loadSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); loadSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); loadSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");
@ -253,7 +252,6 @@ void Launcher::AdvancedPage::saveSettings()
{ {
// Game mechanics // Game mechanics
{ {
saveSettingBool(toggleSneakCheckBox, "toggle sneak", "Input");
saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game"); saveSettingBool(canLootDuringDeathAnimationCheckBox, "can loot during death animation", "Game");
saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game"); saveSettingBool(followersAttackOnSightCheckBox, "followers attack on sight", "Game");
saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game"); saveSettingBool(rebalanceSoulGemValuesCheckBox, "rebalance soul gem values", "Game");

@ -46,7 +46,6 @@ namespace MWBase
virtual void setDragDrop(bool dragDrop) = 0; virtual void setDragDrop(bool dragDrop) = 0;
virtual void setGamepadGuiCursorEnabled(bool enabled) = 0; virtual void setGamepadGuiCursorEnabled(bool enabled) = 0;
virtual void setAttemptJump(bool jumping) = 0;
virtual void toggleControlSwitch(std::string_view sw, bool value) = 0; virtual void toggleControlSwitch(std::string_view sw, bool value) = 0;
virtual bool getControlSwitch(std::string_view sw) = 0; virtual bool getControlSwitch(std::string_view sw) = 0;

@ -68,6 +68,7 @@ namespace MWBase
bool mJump = false; bool mJump = false;
bool mRun = false; bool mRun = false;
bool mSneak = false;
float mMovement = 0; float mMovement = 0;
float mSideMovement = 0; float mSideMovement = 0;
float mPitchChange = 0; float mPitchChange = 0;

@ -155,7 +155,6 @@ namespace MWBase
virtual MWGui::CountDialog* getCountDialog() = 0; virtual MWGui::CountDialog* getCountDialog() = 0;
virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0;
virtual MWGui::TradeWindow* getTradeWindow() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0;
virtual const std::vector<std::unique_ptr<MWGui::MessageBox>>& getActiveMessageBoxes() const = 0;
virtual MWGui::PostProcessorHud* getPostProcessorHud() = 0; virtual MWGui::PostProcessorHud* getPostProcessorHud() = 0;
/// Make the player use an item, while updating GUI state accordingly /// Make the player use an item, while updating GUI state accordingly

@ -53,6 +53,7 @@
#include <components/lua_ui/util.hpp> #include <components/lua_ui/util.hpp>
#include "../mwbase/inputmanager.hpp" #include "../mwbase/inputmanager.hpp"
#include "../mwbase/luamanager.hpp"
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
#include "../mwbase/statemanager.hpp" #include "../mwbase/statemanager.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
@ -794,11 +795,6 @@ namespace MWGui
mMessageBoxManager->removeStaticMessageBox(); mMessageBoxManager->removeStaticMessageBox();
} }
const std::vector<std::unique_ptr<MWGui::MessageBox>>& WindowManager::getActiveMessageBoxes() const
{
return mMessageBoxManager->getActiveMessageBoxes();
}
int WindowManager::readPressedButton() int WindowManager::readPressedButton()
{ {
return mMessageBoxManager->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. // 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. // Otherwise we will get message spam when stealing several items via Take All button.
const MWWorld::Ptr player = MWMechanics::getPlayer(); 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 (currentBounty != mPlayerBounty)
{ {
if (mPlayerBounty >= 0 && currentBounty > mPlayerBounty) if (mPlayerBounty >= 0 && currentBounty > mPlayerBounty)
@ -913,6 +910,26 @@ namespace MWGui
mPlayerBounty = currentBounty; 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<MWGui::MessageBox>& 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(); mDragAndDrop->onFrame();
mHud->onFrame(frameDuration); mHud->onFrame(frameDuration);

@ -179,7 +179,6 @@ namespace MWGui
MWGui::CountDialog* getCountDialog() override; MWGui::CountDialog* getCountDialog() override;
MWGui::ConfirmationDialog* getConfirmationDialog() override; MWGui::ConfirmationDialog* getConfirmationDialog() override;
MWGui::TradeWindow* getTradeWindow() override; MWGui::TradeWindow* getTradeWindow() override;
const std::vector<std::unique_ptr<MWGui::MessageBox>>& getActiveMessageBoxes() const override;
MWGui::PostProcessorHud* getPostProcessorHud() override; MWGui::PostProcessorHud* getPostProcessorHud() override;
/// Make the player use an item, while updating GUI state accordingly /// Make the player use an item, while updating GUI state accordingly

@ -36,99 +36,12 @@ namespace MWInput
, mViewer(viewer) , mViewer(viewer)
, mScreenCaptureHandler(screenCaptureHandler) , mScreenCaptureHandler(screenCaptureHandler)
, mScreenCaptureOperation(screenCaptureOperation) , mScreenCaptureOperation(screenCaptureOperation)
, mAlwaysRunActive(Settings::Manager::getBool("always run", "Input"))
, mAttemptJump(false)
, mTimeIdle(0.f) , 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<MWGui::MessageBox>& 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) if (mBindingsManager->actionIsActive(A_MoveForward) || mBindingsManager->actionIsActive(A_MoveBackward)
|| mBindingsManager->actionIsActive(A_MoveLeft) || mBindingsManager->actionIsActive(A_MoveRight) || mBindingsManager->actionIsActive(A_MoveLeft) || mBindingsManager->actionIsActive(A_MoveRight)
|| mBindingsManager->actionIsActive(A_Jump) || mBindingsManager->actionIsActive(A_Sneak) || mBindingsManager->actionIsActive(A_Jump) || mBindingsManager->actionIsActive(A_Sneak)
@ -139,8 +52,6 @@ namespace MWInput
} }
else else
mTimeIdle += dt; mTimeIdle += dt;
mAttemptJump = false;
} }
void ActionManager::resetIdleTime() void ActionManager::resetIdleTime()
@ -181,21 +92,9 @@ namespace MWInput
case A_Journal: case A_Journal:
toggleJournal(); toggleJournal();
break; break;
case A_AutoMove:
toggleAutoMove();
break;
case A_AlwaysRun:
toggleWalking();
break;
case A_ToggleWeapon:
toggleWeapon();
break;
case A_Rest: case A_Rest:
rest(); rest();
break; break;
case A_ToggleSpell:
toggleSpell();
break;
case A_QuickKey1: case A_QuickKey1:
quickKey(1); quickKey(1);
break; break;
@ -260,13 +159,6 @@ namespace MWInput
if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Inventory)) if (checkAllowedToUseItems() && windowManager->isAllowed(MWGui::GW_Inventory))
MWBase::Environment::get().getWindowManager()->cycleWeapon(true); MWBase::Environment::get().getWindowManager()->cycleWeapon(true);
break; 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() void ActionManager::quickLoad()
{ {
if (!MyGUI::InputManager::getInstance().isModalAny()) if (!MyGUI::InputManager::getInstance().isModalAny())
@ -376,32 +238,6 @@ namespace MWInput
MWBase::Environment::get().getStateManager()->quickSave(); 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() void ActionManager::rest()
{ {
if (!MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols")) 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 bool ActionManager::isSneaking() const
{ {
const MWBase::Environment& env = MWBase::Environment::get(); const MWBase::Environment& env = MWBase::Environment::get();
return env.getMechanicsManager()->isSneaking(env.getWorld()->getPlayer().getPlayer()); 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) void ActionManager::handleGuiArrowKey(int action)
{ {
bool joystickUsed = MWBase::Environment::get().getInputManager()->joystickLastUsed(); bool joystickUsed = MWBase::Environment::get().getInputManager()->joystickLastUsed();

@ -21,23 +21,18 @@ namespace MWInput
osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation, osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation,
osg::ref_ptr<osgViewer::Viewer> viewer, osg::ref_ptr<osgViewer::ScreenCaptureHandler> screenCaptureHandler); osg::ref_ptr<osgViewer::Viewer> viewer, osg::ref_ptr<osgViewer::ScreenCaptureHandler> screenCaptureHandler);
void update(float dt, bool triedToMove); void update(float dt);
void executeAction(int action); void executeAction(int action);
bool checkAllowedToUseItems() const; bool checkAllowedToUseItems() const;
void toggleMainMenu(); void toggleMainMenu();
void toggleSpell();
void toggleWeapon();
void toggleInventory(); void toggleInventory();
void toggleConsole(); void toggleConsole();
void screenshot(); void screenshot();
void toggleJournal(); void toggleJournal();
void activate(); void activate();
void toggleWalking();
void toggleSneaking();
void toggleAutoMove();
void rest(); void rest();
void quickLoad(); void quickLoad();
void quickSave(); void quickSave();
@ -48,11 +43,8 @@ namespace MWInput
void resetIdleTime(); void resetIdleTime();
float getIdleTime() const { return mTimeIdle; } float getIdleTime() const { return mTimeIdle; }
bool isAlwaysRunActive() const { return mAlwaysRunActive; }
bool isSneaking() const; bool isSneaking() const;
void setAttemptJump(bool enabled) { mAttemptJump = enabled; }
private: private:
void handleGuiArrowKey(int action); void handleGuiArrowKey(int action);
@ -61,9 +53,6 @@ namespace MWInput
osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler; osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler;
osgViewer::ScreenCaptureHandler::CaptureOperation* mScreenCaptureOperation; osgViewer::ScreenCaptureHandler::CaptureOperation* mScreenCaptureOperation;
bool mAlwaysRunActive;
bool mAttemptJump;
float mTimeIdle; float mTimeIdle;
}; };
} }

@ -681,65 +681,25 @@ namespace MWInput
void BindingsManager::actionValueChanged(int action, float currentValue, float previousValue) 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) if (mDragDrop && action != A_GameMenu && action != A_Inventory)
return; 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 if (action == A_Use && actionIsActive(A_ToggleWeapon))
} action = A_CycleWeaponRight;
// Otherwise only trigger button presses as they go through specific points else if (action == A_Use && actionIsActive(A_ToggleSpell))
else if (previousValue >= 0.8 && currentValue < 0.8) action = A_CycleSpellRight;
{ else if (action == A_Jump && actionIsActive(A_ToggleWeapon))
currentValue = 0.0; action = A_CycleWeaponLeft;
previousValue = 1.0; else if (action == A_Jump && actionIsActive(A_ToggleSpell))
} action = A_CycleSpellLeft;
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 (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) if (previousValue <= 0.6 && currentValue > 0.6)
MWBase::Environment::get().getInputManager()->executeAction(action); manager->executeAction(action);
} }
} }

@ -25,20 +25,16 @@
namespace MWInput namespace MWInput
{ {
ControllerManager::ControllerManager(BindingsManager* bindingsManager, ActionManager* actionManager, ControllerManager::ControllerManager(BindingsManager* bindingsManager, MouseManager* mouseManager,
MouseManager* mouseManager, const std::filesystem::path& userControllerBindingsFile, const std::filesystem::path& userControllerBindingsFile, const std::filesystem::path& controllerBindingsFile)
const std::filesystem::path& controllerBindingsFile)
: mBindingsManager(bindingsManager) : mBindingsManager(bindingsManager)
, mActionManager(actionManager)
, mMouseManager(mouseManager) , mMouseManager(mouseManager)
, mJoystickEnabled(Settings::Manager::getBool("enable controller", "Input")) , mJoystickEnabled(Settings::Manager::getBool("enable controller", "Input"))
, mGyroAvailable(false) , mGyroAvailable(false)
, mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input")) , mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input"))
, mSneakToggleShortcutTimer(0.f)
, mGamepadGuiCursorEnabled(true) , mGamepadGuiCursorEnabled(true)
, mGuiCursorEnabled(true) , mGuiCursorEnabled(true)
, mJoystickLastUsed(false) , mJoystickLastUsed(false)
, mSneakGamepadShortcut(false)
{ {
if (!controllerBindingsFile.empty()) if (!controllerBindingsFile.empty())
{ {
@ -82,7 +78,7 @@ namespace MWInput
} }
} }
bool ControllerManager::update(float dt) void ControllerManager::update(float dt)
{ {
if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled)) if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled))
{ {
@ -108,77 +104,18 @@ namespace MWInput
} }
} }
// Disable movement in Gui mode if (!MWBase::Environment::get().getWindowManager()->isGuiMode()
if (MWBase::Environment::get().getWindowManager()->isGuiMode() && MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_Running
|| MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) && MWBase::Environment::get().getInputManager()->getControlSwitch("playercontrols"))
{
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"))
{ {
float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight); float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight);
float yAxis = mBindingsManager->getActionValue(A_MoveForwardBackward); float yAxis = mBindingsManager->getActionValue(A_MoveForwardBackward);
if (xAxis != 0.5) if (xAxis != 0.5 || yAxis != 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)
{ {
mJoystickLastUsed = true; mJoystickLastUsed = true;
MWBase::Environment::get().getInputManager()->resetIdleTime(); 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) void ControllerManager::buttonPressed(int deviceID, const SDL_ControllerButtonEvent& arg)

@ -9,20 +9,19 @@
namespace MWInput namespace MWInput
{ {
class ActionManager;
class BindingsManager; class BindingsManager;
class MouseManager; class MouseManager;
class ControllerManager : public SDLUtil::ControllerListener class ControllerManager : public SDLUtil::ControllerListener
{ {
public: public:
ControllerManager(BindingsManager* bindingsManager, ActionManager* actionManager, MouseManager* mouseManager, ControllerManager(BindingsManager* bindingsManager, MouseManager* mouseManager,
const std::filesystem::path& userControllerBindingsFile, const std::filesystem::path& userControllerBindingsFile,
const std::filesystem::path& controllerBindingsFile); const std::filesystem::path& controllerBindingsFile);
virtual ~ControllerManager() = default; virtual ~ControllerManager() = default;
bool update(float dt); void update(float dt);
void buttonPressed(int deviceID, const SDL_ControllerButtonEvent& arg) override; void buttonPressed(int deviceID, const SDL_ControllerButtonEvent& arg) override;
void buttonReleased(int deviceID, const SDL_ControllerButtonEvent& arg) override; void buttonReleased(int deviceID, const SDL_ControllerButtonEvent& arg) override;
@ -58,17 +57,14 @@ namespace MWInput
void enableGyroSensor(); void enableGyroSensor();
BindingsManager* mBindingsManager; BindingsManager* mBindingsManager;
ActionManager* mActionManager;
MouseManager* mMouseManager; MouseManager* mMouseManager;
bool mJoystickEnabled; bool mJoystickEnabled;
bool mGyroAvailable; bool mGyroAvailable;
float mGamepadCursorSpeed; float mGamepadCursorSpeed;
float mSneakToggleShortcutTimer;
bool mGamepadGuiCursorEnabled; bool mGamepadGuiCursorEnabled;
bool mGuiCursorEnabled; bool mGuiCursorEnabled;
bool mJoystickLastUsed; bool mJoystickLastUsed;
bool mSneakGamepadShortcut;
}; };
} }
#endif #endif

@ -39,24 +39,10 @@ namespace MWInput
void ControlSwitch::set(std::string_view key, bool value) void ControlSwitch::set(std::string_view key, bool value)
{ {
MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); if (key == "playerlooking" && !value)
/// \note 7 switches at all, if-else is relevant
if (key == "playercontrols" && !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); auto it = mSwitches.find(key);
if (it == mSwitches.end()) if (it == mSwitches.end())

@ -37,8 +37,8 @@ namespace MWInput
mBindingsManager.get(), screenCaptureOperation, viewer, screenCaptureHandler)) mBindingsManager.get(), screenCaptureOperation, viewer, screenCaptureHandler))
, mKeyboardManager(std::make_unique<KeyboardManager>(mBindingsManager.get())) , mKeyboardManager(std::make_unique<KeyboardManager>(mBindingsManager.get()))
, mMouseManager(std::make_unique<MouseManager>(mBindingsManager.get(), mInputWrapper.get(), window)) , mMouseManager(std::make_unique<MouseManager>(mBindingsManager.get(), mInputWrapper.get(), window))
, mControllerManager(std::make_unique<ControllerManager>(mBindingsManager.get(), mActionManager.get(), , mControllerManager(std::make_unique<ControllerManager>(
mMouseManager.get(), userControllerBindingsFile, controllerBindingsFile)) mBindingsManager.get(), mMouseManager.get(), userControllerBindingsFile, controllerBindingsFile))
, mSensorManager(std::make_unique<SensorManager>()) , mSensorManager(std::make_unique<SensorManager>())
, mGyroManager(std::make_unique<GyroManager>()) , mGyroManager(std::make_unique<GyroManager>())
{ {
@ -57,11 +57,6 @@ namespace MWInput
InputManager::~InputManager() {} InputManager::~InputManager() {}
void InputManager::setAttemptJump(bool jumping)
{
mActionManager->setAttemptJump(jumping);
}
void InputManager::update(float dt, bool disableControls, bool disableEvents) void InputManager::update(float dt, bool disableControls, bool disableEvents)
{ {
mControlsDisabled = disableControls; mControlsDisabled = disableControls;
@ -79,10 +74,10 @@ namespace MWInput
mMouseManager->updateCursorMode(); mMouseManager->updateCursorMode();
bool controllerMove = mControllerManager->update(dt); mControllerManager->update(dt);
mMouseManager->update(dt); mMouseManager->update(dt);
mSensorManager->update(dt); mSensorManager->update(dt);
mActionManager->update(dt, controllerMove); mActionManager->update(dt);
if (mGyroManager->isEnabled()) if (mGyroManager->isEnabled())
{ {

@ -68,7 +68,6 @@ namespace MWInput
void setDragDrop(bool dragDrop) override; void setDragDrop(bool dragDrop) override;
void setGamepadGuiCursorEnabled(bool enabled) override; void setGamepadGuiCursorEnabled(bool enabled) override;
void setAttemptJump(bool jumping) override;
void toggleControlSwitch(std::string_view sw, bool value) override; void toggleControlSwitch(std::string_view sw, bool value) override;
bool getControlSwitch(std::string_view sw) override; bool getControlSwitch(std::string_view sw) override;

@ -48,6 +48,7 @@ namespace MWLua
controls["pitchChange"] = CONTROL(float, mPitchChange); controls["pitchChange"] = CONTROL(float, mPitchChange);
controls["yawChange"] = CONTROL(float, mYawChange); controls["yawChange"] = CONTROL(float, mYawChange);
controls["run"] = CONTROL(bool, mRun); controls["run"] = CONTROL(bool, mRun);
controls["sneak"] = CONTROL(bool, mSneak);
controls["jump"] = CONTROL(bool, mJump); controls["jump"] = CONTROL(bool, mJump);
controls["use"] = CONTROL(int, mUse); controls["use"] = CONTROL(int, mUse);
#undef CONTROL #undef CONTROL

@ -48,7 +48,7 @@ namespace MWLua
{ {
auto* lua = context.mLua; auto* lua = context.mLua;
sol::table api(lua->sol(), sol::create); sol::table api(lua->sol(), sol::create);
api["API_REVISION"] = 30; api["API_REVISION"] = 31;
api["quit"] = [lua]() { api["quit"] = [lua]() {
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
MWBase::Environment::get().getStateManager()->requestQuit(); MWBase::Environment::get().getStateManager()->requestQuit();

@ -3,6 +3,7 @@
#include <components/detournavigator/agentbounds.hpp> #include <components/detournavigator/agentbounds.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <apps/openmw/mwbase/mechanicsmanager.hpp>
#include <apps/openmw/mwmechanics/creaturestats.hpp> #include <apps/openmw/mwmechanics/creaturestats.hpp>
#include <apps/openmw/mwmechanics/drawstate.hpp> #include <apps/openmw/mwmechanics/drawstate.hpp>
#include <apps/openmw/mwworld/class.hpp> #include <apps/openmw/mwworld/class.hpp>
@ -150,6 +151,38 @@ namespace MWLua
else else
throw std::runtime_error("Actor expected"); 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<int>(MWMechanics::DrawState::Nothing)
&& stance != static_cast<int>(MWMechanics::DrawState::Weapon)
&& stance != static_cast<int>(MWMechanics::DrawState::Spell))
{
throw std::runtime_error("Incorrect stance");
}
MWMechanics::DrawState newDrawState = static_cast<MWMechanics::DrawState>(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) { actor["canMove"] = [](const Object& o) {
const MWWorld::Class& cls = o.ptr().getClass(); const MWWorld::Class& cls = o.ptr().getClass();

@ -5,6 +5,8 @@
#include <apps/openmw/mwbase/environment.hpp> #include <apps/openmw/mwbase/environment.hpp>
#include <apps/openmw/mwbase/world.hpp> #include <apps/openmw/mwbase/world.hpp>
#include <apps/openmw/mwmechanics/npcstats.hpp>
#include <apps/openmw/mwworld/class.hpp>
#include <apps/openmw/mwworld/esmstore.hpp> #include <apps/openmw/mwworld/esmstore.hpp>
#include "../stats.hpp" #include "../stats.hpp"
@ -43,5 +45,14 @@ namespace MWLua
= sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHair.getRefIdString(); }); = sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHair.getRefIdString(); });
record["head"] record["head"]
= sol::readonly_property([](const ESM::NPC& rec) -> std::string { return rec.mHead.getRefIdString(); }); = 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");
};
} }
} }

@ -352,6 +352,7 @@ namespace MWMechanics
const float rotationZ = mov.mRotation[2]; const float rotationZ = mov.mRotation[2];
const bool jump = mov.mPosition[2] == 1; const bool jump = mov.mPosition[2] == 1;
const bool runFlag = stats.getMovementFlag(MWMechanics::CreatureStats::Flag_Run); const bool runFlag = stats.getMovementFlag(MWMechanics::CreatureStats::Flag_Run);
const bool sneakFlag = stats.getMovementFlag(MWMechanics::CreatureStats::Flag_Sneak);
const bool attackingOrSpell = stats.getAttackingOrSpell(); const bool attackingOrSpell = stats.getAttackingOrSpell();
if (controls.mChanged) if (controls.mChanged)
{ {
@ -363,16 +364,24 @@ namespace MWMechanics
mov.mRotation[2] = controls.mYawChange; mov.mRotation[2] = controls.mYawChange;
mov.mSpeedFactor = osg::Vec2(controls.mMovement, controls.mSideMovement).length(); mov.mSpeedFactor = osg::Vec2(controls.mMovement, controls.mSideMovement).length();
stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, controls.mRun); stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Run, controls.mRun);
stats.setMovementFlag(MWMechanics::CreatureStats::Flag_Sneak, controls.mSneak);
stats.setAttackingOrSpell((controls.mUse & 1) == 1); stats.setAttackingOrSpell((controls.mUse & 1) == 1);
controls.mChanged = false; controls.mChanged = false;
} }
controls.mSideMovement = movement.x(); // For the player we don't need to copy these values to Lua because mwinput doesn't change them.
controls.mMovement = movement.y(); // 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.mPitchChange = rotationX;
controls.mYawChange = rotationZ; controls.mYawChange = rotationZ;
controls.mJump = jump;
controls.mRun = runFlag;
controls.mUse = attackingOrSpell ? controls.mUse | 1 : controls.mUse & ~1;
} }
} }

@ -1907,13 +1907,6 @@ namespace MWMechanics
movementSettings.mSpeedFactor = std::min(vec.length(), 1.f); movementSettings.mSpeedFactor = std::min(vec.length(), 1.f);
vec.normalize(); 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"); static const bool smoothMovement = Settings::Manager::getBool("smooth movement", "Game");
if (smoothMovement) if (smoothMovement)
{ {

@ -40,8 +40,6 @@ namespace MWWorld
, mLastKnownExteriorPosition(0, 0, 0) , mLastKnownExteriorPosition(0, 0, 0)
, mMarkedPosition(ESM::Position()) , mMarkedPosition(ESM::Position())
, mMarkedCell(nullptr) , mMarkedCell(nullptr)
, mAutoMove(false)
, mForwardBackward(0)
, mTeleported(false) , mTeleported(false)
, mCurrentCrimeId(-1) , mCurrentCrimeId(-1)
, mPaidCrimeId(-1) , mPaidCrimeId(-1)
@ -163,61 +161,6 @@ namespace MWWorld
ptr.getClass().getNpcStats(ptr).setDrawState(state); 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<float>(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) void Player::yaw(float yaw)
{ {
MWWorld::Ptr ptr = getPlayer(); MWWorld::Ptr ptr = getPlayer();
@ -272,11 +215,6 @@ namespace MWWorld
mTeleported = teleported; mTeleported = teleported;
} }
void Player::setAttackingOrSpell(bool attackingOrSpell)
{
getPlayer().getClass().getCreatureStats(getPlayer()).setAttackingOrSpell(attackingOrSpell);
}
void Player::setJumping(bool jumping) void Player::setJumping(bool jumping)
{ {
mJumping = jumping; mJumping = jumping;
@ -315,8 +253,6 @@ namespace MWWorld
mCellStore = nullptr; mCellStore = nullptr;
mSign = ESM::RefId::sEmpty; mSign = ESM::RefId::sEmpty;
mMarkedCell = nullptr; mMarkedCell = nullptr;
mAutoMove = false;
mForwardBackward = 0;
mTeleported = false; mTeleported = false;
mJumping = false; mJumping = false;
mCurrentCrimeId = -1; mCurrentCrimeId = -1;
@ -475,7 +411,6 @@ namespace MWWorld
mMarkedCell = nullptr; mMarkedCell = nullptr;
} }
mForwardBackward = 0;
mTeleported = false; mTeleported = false;
mPreviousItems = player.mPreviousItems; mPreviousItems = player.mPreviousItems;

@ -41,8 +41,6 @@ namespace MWWorld
// If no position was marked, this is nullptr // If no position was marked, this is nullptr
CellStore* mMarkedCell; CellStore* mMarkedCell;
bool mAutoMove;
float mForwardBackward;
bool mTeleported; bool mTeleported;
int mCurrentCrimeId; // the id assigned witnesses int mCurrentCrimeId; // the id assigned witnesses
@ -90,17 +88,6 @@ namespace MWWorld
/// Activate the object under the crosshair, if any /// Activate the object under the crosshair, if any
void activate(); 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 yaw(float yaw);
void pitch(float pitch); void pitch(float pitch);
void roll(float roll); void roll(float roll);
@ -108,8 +95,6 @@ namespace MWWorld
bool wasTeleported() const; bool wasTeleported() const;
void setTeleported(bool teleported); void setTeleported(bool teleported);
void setAttackingOrSpell(bool attackingOrSpell);
void setJumping(bool jumping); void setJumping(bool jumping);
bool getJumping() const; bool getJumping() const;

@ -3651,7 +3651,7 @@ namespace MWWorld
{ {
if (MWBase::Environment::get().getMechanicsManager()->isAttackPreparing(player)) if (MWBase::Environment::get().getMechanicsManager()->isAttackPreparing(player))
{ {
mPlayer->setAttackingOrSpell(false); player.getClass().getCreatureStats(player).setAttackingOrSpell(false);
} }
mPlayer->setDrawState(MWMechanics::DrawState::Nothing); mPlayer->setDrawState(MWMechanics::DrawState::Nothing);

@ -26,6 +26,7 @@ $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw/*lua
cd $FILES_DIR/data cd $FILES_DIR/data
$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR openmw_aux/*lua $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/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/camera/camera.lua
$DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/mwui/init.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 $DOCUMENTOR_PATH -f doc -d $OUTPUT_DIR scripts/omw/settings/player.lua

@ -29,6 +29,7 @@ Lua API reference
openmw_aux_ui openmw_aux_ui
interface_ai interface_ai
interface_camera interface_camera
interface_controls
interface_mwui interface_mwui
interface_settings interface_settings
iterables iterables
@ -73,6 +74,10 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid
- by player scripts - by player scripts
- | Allows to alter behavior of the built-in camera script - | Allows to alter behavior of the built-in camera script
| without overriding the script completely. | without overriding the script completely.
* - :ref:`Controls <Interface Controls>`
- by player scripts
- | Allows to alter behavior of the built-in script
| that handles player controls.
* - :ref:`Settings <Interface Settings>` * - :ref:`Settings <Interface Settings>`
- by player and global scripts - by player and global scripts
- Save, display and track changes of setting values. - Save, display and track changes of setting values.

@ -0,0 +1,6 @@
Interface Controls
==================
.. raw:: html
:file: generated_html/scripts_omw_playercontrols.html

@ -452,6 +452,10 @@ The order in which the scripts are started is important. So if one mod should ov
- by player scripts - by player scripts
- | Allows to alter behavior of the built-in camera script - | Allows to alter behavior of the built-in camera script
| without overriding the script completely. | without overriding the script completely.
* - :ref:`Controls <Interface Controls>`
- by player scripts
- | Allows to alter behavior of the built-in script
| that handles player controls.
* - :ref:`Settings <Interface Settings>` * - :ref:`Settings <Interface Settings>`
- by player and global scripts - by player and global scripts
- Save, display and track changes of setting values. - Save, display and track changes of setting values.

@ -38,7 +38,9 @@ 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. 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. 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". **This setting is removed from settings.cfg.**
Can be configured in game in the settings menu.
always run always run
---------- ----------
@ -52,7 +54,9 @@ The shift key will temporarily invert this setting, and the caps lock key will i
This setting is updated every time you exit the game, 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. 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. **This setting is removed from settings.cfg.**
This setting can be toggled in game by pressing the CapsLock key or in the settings menu.
camera sensitivity camera sensitivity
------------------ ------------------

@ -50,6 +50,7 @@ set(BUILTIN_DATA_FILES
l10n/OMWCamera/ru.yaml l10n/OMWCamera/ru.yaml
l10n/OMWCamera/sv.yaml l10n/OMWCamera/sv.yaml
l10n/OMWCamera/fr.yaml l10n/OMWCamera/fr.yaml
l10n/OMWControls/en.yaml
l10n/PostProcessing/de.yaml l10n/PostProcessing/de.yaml
l10n/PostProcessing/en.yaml l10n/PostProcessing/en.yaml
l10n/PostProcessing/ru.yaml l10n/PostProcessing/ru.yaml
@ -83,6 +84,7 @@ set(BUILTIN_DATA_FILES
scripts/omw/console/player.lua scripts/omw/console/player.lua
scripts/omw/console/global.lua scripts/omw/console/global.lua
scripts/omw/console/local.lua scripts/omw/console/local.lua
scripts/omw/playercontrols.lua
scripts/omw/settings/player.lua scripts/omw/settings/player.lua
scripts/omw/settings/global.lua scripts/omw/settings/global.lua
scripts/omw/settings/common.lua scripts/omw/settings/common.lua

@ -1,6 +1,7 @@
PLAYER: scripts/omw/mwui/init.lua PLAYER: scripts/omw/mwui/init.lua
GLOBAL: scripts/omw/settings/global.lua GLOBAL: scripts/omw/settings/global.lua
PLAYER: scripts/omw/settings/player.lua PLAYER: scripts/omw/settings/player.lua
PLAYER: scripts/omw/playercontrols.lua
PLAYER: scripts/omw/camera/camera.lua PLAYER: scripts/omw/camera/camera.lua
NPC,CREATURE: scripts/omw/ai.lua NPC,CREATURE: scripts/omw/ai.lua
PLAYER: scripts/omw/console/player.lua PLAYER: scripts/omw/console/player.lua

@ -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.

@ -84,11 +84,6 @@ local idleTimer = 0
local vanityDelay = core.getGMST('fVanityDelay') local vanityDelay = core.getGMST('fVanityDelay')
local function updateVanity(dt) local function updateVanity(dt)
if input.isIdle() then
idleTimer = idleTimer + dt
else
idleTimer = 0
end
local vanityAllowed = input.getControlSwitch(input.CONTROL_SWITCH.VanityMode) local vanityAllowed = input.getControlSwitch(input.CONTROL_SWITCH.VanityMode)
if vanityAllowed and idleTimer > vanityDelay and camera.getMode() ~= MODE.Vanity then if vanityAllowed and idleTimer > vanityDelay and camera.getMode() ~= MODE.Vanity then
camera.setMode(MODE.Vanity) camera.setMode(MODE.Vanity)
@ -177,8 +172,19 @@ local function onUpdate(dt)
pov_auto_switch.onUpdate(dt) pov_auto_switch.onUpdate(dt)
end 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) local function onFrame(dt)
if core.isWorldPaused() then return end if core.isWorldPaused() then return end
updateIdleTimer(dt)
local mode = camera.getMode() local mode = camera.getMode()
if mode == MODE.FirstPerson or mode == MODE.ThirdPerson then if mode == MODE.FirstPerson or mode == MODE.ThirdPerson then
primaryMode = mode primaryMode = mode

@ -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,
}
}

@ -33,6 +33,7 @@
-- @field [parent=#ActorControls] #number yawChange Turn right (radians); if negative - turn left -- @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] #number pitchChange Look down (radians); if negative - look up
-- @field [parent=#ActorControls] #boolean run true - run, false - walk -- @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] #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. -- @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.

@ -105,6 +105,13 @@
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor
-- @return #number -- @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. -- Returns `true` if the item is equipped on the actor.
-- @function [parent=#Actor] isEquipped -- @function [parent=#Actor] isEquipped
@ -477,6 +484,12 @@
-- @param openmw.core#GameObject object -- @param openmw.core#GameObject object
-- @return #boolean -- @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 -- Returns the read-only @{#NpcRecord} of an NPC
-- @function [parent=#NPC] record -- @function [parent=#NPC] record

@ -504,12 +504,6 @@ soft particles = false
# Capture control of the cursor prevent movement outside the window. # Capture control of the cursor prevent movement outside the window.
grab cursor = true 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 when not in GUI mode. (>0.0, e.g. 0.1 to 5.0).
camera sensitivity = 1.0 camera sensitivity = 1.0

@ -64,12 +64,12 @@
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QCheckBox" name="toggleSneakCheckBox"> <widget class="QLabel" name="deprecatedToggleSneakLabel">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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. &lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Toggle sneak</string> <string>&lt;s&gt;Toggle sneak&lt;/s&gt; (moved to the in-game menu)</string>
</property> </property>
</widget> </widget>
</item> </item>

@ -0,0 +1 @@
# It is an empty file that overrides builtin.omwscripts and disables builtin scripts
Loading…
Cancel
Save