Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/launcher/settingspage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ bool Launcher::SettingsPage::loadSettings()
loadSettingBool(Settings::gui().mStretchMenuBackground, *stretchBackgroundCheckBox);
connect(controllerMenusCheckBox, &QCheckBox::toggled, this, &SettingsPage::slotControllerMenusToggled);
loadSettingBool(Settings::gui().mControllerMenus, *controllerMenusCheckBox);
loadSettingBool(Settings::input().mEnableControllerRumble, *controllerRumbleCheckBox);
loadSettingBool(Settings::gui().mControllerTooltips, *controllerMenuTooltipsCheckBox);
loadSettingBool(Settings::map().mAllowZooming, *useZoomOnMapCheckBox);
loadSettingBool(Settings::game().mGraphicHerbalism, *graphicHerbalismCheckBox);
Expand Down Expand Up @@ -500,6 +501,7 @@ void Launcher::SettingsPage::saveSettings()
saveSettingInt(*showOwnedComboBox, Settings::game().mShowOwned);
saveSettingBool(*stretchBackgroundCheckBox, Settings::gui().mStretchMenuBackground);
saveSettingBool(*controllerMenusCheckBox, Settings::gui().mControllerMenus);
saveSettingBool(*controllerRumbleCheckBox, Settings::input().mEnableControllerRumble);
saveSettingBool(*controllerMenuTooltipsCheckBox, Settings::gui().mControllerTooltips);
saveSettingBool(*useZoomOnMapCheckBox, Settings::map().mAllowZooming);
saveSettingBool(*graphicHerbalismCheckBox, Settings::game().mGraphicHerbalism);
Expand Down
22 changes: 16 additions & 6 deletions apps/launcher/ui/settingspage.ui
Original file line number Diff line number Diff line change
Expand Up @@ -1412,12 +1412,22 @@
<string>Enable Controller Menus</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QCheckBox" name="controllerMenuTooltipsCheckBox">
<property name="enabled">
<bool>false</bool>
</property>
</item>
<item row="8" column="0">
<widget class="QCheckBox" name="controllerRumbleCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable haptic feedback for controllers that support rumble.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable Controller Rumble</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QCheckBox" name="controllerMenuTooltipsCheckBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When using controller menus, make tooltips visible by default.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
Expand Down
4 changes: 4 additions & 0 deletions apps/openmw/mwbase/inputmanager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ namespace MWBase
virtual float getActionValue(int action) const = 0; // returns value in range [0, 1]
virtual bool isControllerButtonPressed(SDL_GameControllerButton button) const = 0;
virtual float getControllerAxisValue(SDL_GameControllerAxis axis) const = 0; // returns value in range [-1, 1]
virtual bool controllerHasRumble() const = 0;
virtual void playControllerRumble(float lowFrequencyStrength, float highFrequencyStrength,
float durationSeconds) = 0;
virtual void stopControllerRumble() = 0;
virtual int getMouseMoveX() const = 0;
virtual int getMouseMoveY() const = 0;
virtual void warpMouseToWidget(MyGUI::Widget* widget) = 0;
Expand Down
4 changes: 4 additions & 0 deletions apps/openmw/mwclass/creature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "../mwmechanics/magiceffects.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/rumble.hpp"
#include "../mwmechanics/setbaseaisetting.hpp"

#include "../mwbase/environment.hpp"
Expand Down Expand Up @@ -453,6 +454,9 @@ namespace MWClass
stats.setHitRecovery(true); // Is this supposed to always occur?
}
}

if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer() && hasHealthDamage && healthDamage > 0.0f)
MWMechanics::Rumble::onPlayerDealtDamage(healthDamage);
}

std::unique_ptr<MWWorld::Action> Creature::activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const
Expand Down
10 changes: 10 additions & 0 deletions apps/openmw/mwclass/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "../mwmechanics/inventory.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/rumble.hpp"
#include "../mwmechanics/setbaseaisetting.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/weapontype.hpp"
Expand Down Expand Up @@ -817,7 +818,16 @@ namespace MWClass
if (hasHealthDamage && healthDamage > 0.0f)
{
if (ptr == MWMechanics::getPlayer())
{
MWBase::Environment::get().getWindowManager()->activateHitOverlay();
MWMechanics::Rumble::onPlayerDamaged(ptr, healthDamage);
}
}

if (!attacker.isEmpty() && attacker == MWMechanics::getPlayer() && hasHealthDamage && healthDamage > 0.0f
&& ptr != MWMechanics::getPlayer())
{
MWMechanics::Rumble::onPlayerDealtDamage(healthDamage);
}

if (!wasDead && getCreatureStats(ptr).isDead())
Expand Down
145 changes: 145 additions & 0 deletions apps/openmw/mwinput/controllermanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

#include <SDL.h>

#include <algorithm>
#include <cmath>

#include <components/debug/debuglog.hpp>
#include <components/esm/refid.hpp>
#include <components/files/conversion.hpp>
Expand Down Expand Up @@ -33,6 +36,9 @@ namespace MWInput
, mGuiCursorEnabled(true)
, mJoystickLastUsed(false)
, mGamepadMousePressed(false)
, mActiveController(nullptr)
, mControllerHasRumble(false)
, mRumbleState()
{
if (!controllerBindingsFile.empty())
{
Expand Down Expand Up @@ -80,10 +86,15 @@ namespace MWInput
}

mBindingsManager->setJoystickDeadZone(Settings::input().mJoystickDeadZone);

refreshActiveController();
}

void ControllerManager::update(float dt)
{
refreshActiveController();
updateRumble(dt);

if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled))
{
float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight) * 2.0f - 1.0f;
Expand Down Expand Up @@ -243,11 +254,145 @@ namespace MWInput
{
mBindingsManager->controllerAdded(deviceID, arg);
enableGyroSensor();
refreshActiveController();
}

void ControllerManager::controllerRemoved(const SDL_ControllerDeviceEvent& arg)
{
mBindingsManager->controllerRemoved(arg);
refreshActiveController();
}

void ControllerManager::refreshActiveController()
{
SDL_GameController* controller = mBindingsManager->getControllerOrNull();
if (controller == mActiveController)
return;

if (mActiveController != nullptr)
{
stopRumbleInternal();
clearRumbleState();
}

mActiveController = controller;
#if SDL_VERSION_ATLEAST(2, 0, 9)
if (mActiveController)
mControllerHasRumble = SDL_GameControllerHasRumble(mActiveController) == SDL_TRUE;
else
mControllerHasRumble = false;
#else
mControllerHasRumble = false;
#endif
}

void ControllerManager::playRumble(
float lowFrequencyStrength, float highFrequencyStrength, float durationSeconds)
{
refreshActiveController();

if (!Settings::input().mEnableController || !Settings::input().mEnableControllerRumble)
return;

if (!mActiveController || !mControllerHasRumble)
return;

durationSeconds = std::max(durationSeconds, 0.0f);
if (durationSeconds == 0.0f)
{
stopRumbleInternal();
clearRumbleState();
return;
}

const float strengthScale = Settings::input().mControllerRumbleStrength;
const float lowScaled = std::clamp(lowFrequencyStrength, 0.0f, 1.0f) * strengthScale;
const float highScaled = std::clamp(highFrequencyStrength, 0.0f, 1.0f) * strengthScale;

if (lowScaled <= 0.0f && highScaled <= 0.0f)
{
stopRumbleInternal();
clearRumbleState();
return;
}

const float limitedDuration = std::min(durationSeconds, 30.0f);
const Uint32 durationMs = static_cast<Uint32>(std::round(limitedDuration * 1000.0f));
if (durationMs == 0)
{
stopRumbleInternal();
clearRumbleState();
return;
}

const Uint16 lowAmplitude = static_cast<Uint16>(std::round(std::clamp(lowScaled, 0.0f, 1.0f) * 65535.0f));
const Uint16 highAmplitude = static_cast<Uint16>(std::round(std::clamp(highScaled, 0.0f, 1.0f) * 65535.0f));
#if SDL_VERSION_ATLEAST(2, 0, 9)
if (SDL_GameControllerRumble(mActiveController, lowAmplitude, highAmplitude, durationMs) != 0)
{
Log(Debug::Warning) << "Failed to start controller rumble: " << SDL_GetError();
clearRumbleState();
return;
}
#else
Log(Debug::Warning) << "Controller rumble requested but SDL version does not support it";
clearRumbleState();
return;
#endif

mRumbleState.mActive = true;
mRumbleState.mLowStrength = lowScaled;
mRumbleState.mHighStrength = highScaled;
mRumbleState.mRemainingTime = limitedDuration;
}

void ControllerManager::stopRumble()
{
refreshActiveController();
stopRumbleInternal();
clearRumbleState();
}

void ControllerManager::updateRumble(float dt)
{
if (!mRumbleState.mActive)
return;

if (!Settings::input().mEnableController || !Settings::input().mEnableControllerRumble || !mActiveController
|| !mControllerHasRumble)
{
stopRumbleInternal();
clearRumbleState();
return;
}

if (dt > 0.0f)
{
mRumbleState.mRemainingTime = std::max(0.0f, mRumbleState.mRemainingTime - dt);
if (mRumbleState.mRemainingTime <= 0.0f)
{
stopRumbleInternal();
clearRumbleState();
}
}
}

void ControllerManager::stopRumbleInternal()
{
if (!mActiveController)
return;

#if SDL_VERSION_ATLEAST(2, 0, 9)
if (SDL_GameControllerRumble(mActiveController, 0, 0, 0) != 0)
Log(Debug::Warning) << "Failed to stop controller rumble: " << SDL_GetError();
#else
SDL_GameControllerRumble(mActiveController, 0, 0, 0);
#endif
}

void ControllerManager::clearRumbleState()
{
mRumbleState = {};
}

bool ControllerManager::gamepadToGuiControl(const SDL_ControllerButtonEvent& arg)
Expand Down
20 changes: 20 additions & 0 deletions apps/openmw/mwinput/controllermanager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <filesystem>
#include <string>

#include <SDL_gamecontroller.h>

#include <components/sdlutil/events.hpp>
#include <components/settings/settings.hpp>

Expand Down Expand Up @@ -37,6 +39,10 @@ namespace MWInput
void setJoystickLastUsed(bool enabled) { mJoystickLastUsed = enabled; }
bool joystickLastUsed() const { return mJoystickLastUsed; }

bool controllerHasRumble() const { return mControllerHasRumble; }
void playRumble(float lowFrequencyStrength, float highFrequencyStrength, float durationSeconds);
void stopRumble();

void setGuiCursorEnabled(bool enabled) { mGuiCursorEnabled = enabled; }

void setGamepadGuiCursorEnabled(bool enabled) { mGamepadGuiCursorEnabled = enabled; }
Expand All @@ -59,6 +65,10 @@ namespace MWInput
void enableGyroSensor();

int getControllerType();
void refreshActiveController();
void updateRumble(float dt);
void stopRumbleInternal();
void clearRumbleState();

BindingsManager* mBindingsManager;
MouseManager* mMouseManager;
Expand All @@ -68,6 +78,16 @@ namespace MWInput
bool mGuiCursorEnabled;
bool mJoystickLastUsed;
bool mGamepadMousePressed;
SDL_GameController* mActiveController;
bool mControllerHasRumble;
struct RumbleState
{
float mRemainingTime = 0.f;
float mLowStrength = 0.f;
float mHighStrength = 0.f;
bool mActive = false;
};
RumbleState mRumbleState;
};
}
#endif
18 changes: 18 additions & 0 deletions apps/openmw/mwinput/inputmanagerimp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ namespace MWInput

if (disableControls)
{
mControllerManager->update(0.f);
mMouseManager->updateCursorMode();
return;
}
Expand Down Expand Up @@ -179,6 +180,23 @@ namespace MWInput
return mControllerManager->getAxisValue(axis);
}

bool InputManager::controllerHasRumble() const
{
return Settings::input().mEnableController && Settings::input().mEnableControllerRumble
&& mControllerManager->controllerHasRumble();
}

void InputManager::playControllerRumble(
float lowFrequencyStrength, float highFrequencyStrength, float durationSeconds)
{
mControllerManager->playRumble(lowFrequencyStrength, highFrequencyStrength, durationSeconds);
}

void InputManager::stopControllerRumble()
{
mControllerManager->stopRumble();
}

int InputManager::getMouseMoveX() const
{
return mMouseManager->getMouseMoveX();
Expand Down
4 changes: 4 additions & 0 deletions apps/openmw/mwinput/inputmanagerimp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ namespace MWInput
float getActionValue(int action) const override;
bool isControllerButtonPressed(SDL_GameControllerButton button) const override;
float getControllerAxisValue(SDL_GameControllerAxis axis) const override;
bool controllerHasRumble() const override;
void playControllerRumble(float lowFrequencyStrength, float highFrequencyStrength,
float durationSeconds) override;
void stopControllerRumble() override;
int getMouseMoveX() const override;
int getMouseMoveY() const override;
void warpMouseToWidget(MyGUI::Widget* widget) override;
Expand Down
Loading