Skip to content

Commit

Permalink
Implement Adaptive Trigger response for GameInput Impulse Trigger emu…
Browse files Browse the repository at this point in the history
…lation
  • Loading branch information
Kaldaien committed Nov 30, 2024
1 parent 270d73e commit 5b5f325
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
========
+ For all Unreal Engine games that use GameInput, enable Xbox Emulation by
default on first launch.
+ Implement Adaptive Trigger response for GameInput Impulse Trigger emulation,
shooting guns and stabbing stuff in STALKER 2 now has Force Feedback that
would otherwise be Xbox exclusive!

24.11.29.5
==========
Expand Down
13 changes: 12 additions & 1 deletion include/SpecialK/input/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -1041,13 +1041,18 @@ struct SK_HID_PlayStationDevice
volatile ULONG left, right;
volatile ULONG last_set;

struct {
volatile ULONG left;
volatile ULONG right;
} trigger;

volatile ULONG max_val = 0;

// At most, allow the controller to vibrate for 1000 ms without
// some kind of attempt to set a new value... otherwise, it
// will tend to vibrate infinitely.
static constexpr auto MAX_TTL_IN_MSECS = 1000UL;
} _vibration = { 0, 0, 0, 0 };
} _vibration = { 0, 0, 0, 0, 0, 0 };

void setRGB (BYTE red, BYTE green, BYTE blue) {
_color.r = red;
Expand All @@ -1059,6 +1064,12 @@ struct SK_HID_PlayStationDevice
USHORT right,
USHORT max_val = std::numeric_limits <USHORT>::max () );

void setVibration ( USHORT low_freq,
USHORT high_freq,
USHORT left_trigger,
USHORT right_trigger,
USHORT max_val = std::numeric_limits <USHORT>::max () );

bool request_input_report (void);
bool write_output_report (bool force = false);

Expand Down
2 changes: 2 additions & 0 deletions src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3706,6 +3706,8 @@ auto DeclKeybind =
case SK_GAME_ID::Stalker2:
// Stupid game requires Fullscreen Exclusive (in D3D12) for HDR
config.render.dxgi.fake_fullscreen_mode = true;
config.input.gamepad.xinput.emulate = true;
// GameInput has poor support for non-Xbox controllers...
break;

case SK_GAME_ID::Metaphor:
Expand Down
35 changes: 33 additions & 2 deletions src/input/game_input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1007,8 +1007,39 @@ SK_IGameInputDevice::SetRumbleState (GameInputRumbleParams const *params) noexce
float left = (params_.lowFrequency + params_.leftTrigger * config.input.gamepad.impulse_strength_l);
float right = (params_.highFrequency + params_.rightTrigger * config.input.gamepad.impulse_strength_r);

SK_XInput_PulseController ( 0, std::clamp (left, 0.0f, 1.0f),
std::clamp (right, 0.0f, 1.0f) );
if (params_.leftTrigger != 0 && params_.rightTrigger != 0)
{
SK_XInput_PulseController ( 0, std::clamp (left, 0.0f, 1.0f),
std::clamp (right, 0.0f, 1.0f) );
}

else
{
if (config.input.gamepad.xinput.emulate && (! config.input.gamepad.xinput.blackout_api))
{
SK_HID_PlayStationDevice *pNewestInputDevice = nullptr;

for ( auto& controller : SK_HID_PlayStationControllers )
{
if (controller.bConnected)
{
if (pNewestInputDevice == nullptr ||
pNewestInputDevice->xinput.last_active < controller.xinput.last_active)
{
pNewestInputDevice = &controller;
}
}
}

if (pNewestInputDevice != nullptr)
{
pNewestInputDevice->setVibration (std::min (65535ui16, static_cast <USHORT> (params_.lowFrequency * 65536.0f)),
std::min (65535ui16, static_cast <USHORT> (params_.highFrequency * 65536.0f)),
std::min (65535ui16, static_cast <USHORT> (params_.leftTrigger * 65536.0f)),
std::min (65535ui16, static_cast <USHORT> (params_.rightTrigger * 65536.0f)), 65535ui16);
}
}
}
}
}

Expand Down
115 changes: 115 additions & 0 deletions src/input/hid_reports/playstation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,61 @@ struct SK_HID_DualShock4_GetStateDataBt : SK_HID_DualShock4_GetStateData {
};
#pragma pack(pop)

void
SK_HID_PlayStationDevice::setVibration (
USHORT low_freq,
USHORT high_freq,
USHORT left_trigger,
USHORT right_trigger,
USHORT max_val )
{
if (max_val == 0)
{
const auto last_max = ReadULongAcquire (&_vibration.max_val);

InterlockedCompareExchange (
&_vibration.max_val,
std::max ( { last_max,
static_cast <DWORD> (low_freq),
static_cast <DWORD> (high_freq),
static_cast <DWORD> (left_trigger),
static_cast <DWORD> (right_trigger),} ),
last_max
);

if (last_max > 255)
max_val = 65535;
else
max_val = 255;
}

WriteULongRelease (&_vibration.left,
std::min (255UL,
static_cast <ULONG> (
std::clamp (static_cast <double> (low_freq)/
static_cast <double> (max_val), 0.0, 1.0) * 256.0)));

WriteULongRelease (&_vibration.right,
std::min (255UL,
static_cast <ULONG> (
std::clamp (static_cast <double> (high_freq)/
static_cast <double> (max_val), 0.0, 1.0) * 256.0)));

WriteULongRelease (&_vibration.trigger.left,
std::min (255UL,
static_cast <ULONG> (
std::clamp (static_cast <double> (left_trigger)/
static_cast <double> (max_val), 0.0, 1.0) * 256.0)));

WriteULongRelease (&_vibration.trigger.right,
std::min (255UL,
static_cast <ULONG> (
std::clamp (static_cast <double> (right_trigger)/
static_cast <double> (max_val), 0.0, 1.0) * 256.0)));

WriteULongRelease (&_vibration.last_set, SK::ControlPanel::current_time);
}

void
SK_HID_PlayStationDevice::setVibration (
USHORT left,
Expand Down Expand Up @@ -856,6 +911,9 @@ SK_HID_PlayStationDevice::setVibration (
std::clamp (static_cast <double> (right)/
static_cast <double> (max_val), 0.0, 1.0) * 256.0)));

WriteULongRelease (&_vibration.trigger.left, 0);
WriteULongRelease (&_vibration.trigger.right, 0);

WriteULongRelease (&_vibration.last_set, SK::ControlPanel::current_time);
}

Expand Down Expand Up @@ -2607,6 +2665,63 @@ SK_HID_PlayStationDevice::write_output_report (bool force)
ReadULongAcquire (&pDevice->_vibration.left)
);

uint8_t effects[3][11] = {
/* Clear trigger effect */
{ 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
/* Constant resistance across entire trigger pull */
{ 0x01, 0, 110, 0, 0, 0, 0, 0, 0, 0, 0 },
/* Resistance and vibration when trigger is pulled */
{ 0x06, 15, 63, 0, 0, 0, 0, 0, 0, 0, 0 },
};

//static int trigger_effect = 0;
// trigger_effect = (trigger_effect + 1) % 3;

if (pDevice->_vibration.trigger.left != 0)
{
output->AllowLeftTriggerFFB = true;

const auto trigger_effect = 2;
memcpy (output->LeftTriggerFFB, effects [trigger_effect], sizeof (effects [trigger_effect]));

output->LeftTriggerFFB [2] =
static_cast <uint8_t> (std::clamp (
static_cast <float> (pDevice->_vibration.trigger.left) *
config.input.gamepad.impulse_strength_l, 0.0f, 1.0f)
);
}

else
{
output->AllowLeftTriggerFFB = true;

const auto trigger_effect = 0;
memcpy (output->LeftTriggerFFB, effects [trigger_effect], sizeof (effects [trigger_effect]));
}

if (pDevice->_vibration.trigger.right != 0)
{
output->AllowRightTriggerFFB = true;

const auto trigger_effect = 2;
memcpy (output->RightTriggerFFB, effects [trigger_effect], sizeof (effects [trigger_effect]));

output->RightTriggerFFB [2] =
static_cast <uint8_t> (std::clamp (
static_cast <float> (pDevice->_vibration.trigger.right) *
config.input.gamepad.impulse_strength_r, 0.0f, 1.0f)
);
}

else
{
output->AllowRightTriggerFFB = true;

const auto trigger_effect = 0;
memcpy (output->RightTriggerFFB, effects [trigger_effect], sizeof (effects [trigger_effect]));
}


static bool bMuted = SK_IsGameMuted ();
static DWORD dwLastMuteCheck = SK_timeGetTime ();

Expand Down

0 comments on commit 5b5f325

Please sign in to comment.