xan1242 / NFS-XtendedInput

NFS (Black Box, MW & newer) - XInput Plugin
MIT License
101 stars 7 forks source link

jank start button polling #8

Closed TorutheRedFox closed 2 years ago

TorutheRedFox commented 2 years ago

it doesn't pause the game half the time you press it

xan1242 commented 2 years ago

I need more info on this. I'm testing with an Xbox One controller and it works fine.

Pause action polls on key down action only, not on key up.

Also, you can only trigger it once the HUD is considered visible by the game. This was necessary as FE actions interfere with HUD actions.

TorutheRedFox commented 2 years ago

with a DS3 emulating a 360 controller it only sometimes works

xan1242 commented 2 years ago

Sounds like an issue with your controller then. Tested with a DS3 and ScpServer, works fine. Can't replicate the issue, sorry.

Probably the rubber domes under the rubber buttons aren't making a good contact, which is a common issue on DS2 and DS3.

TorutheRedFox commented 2 years ago

I've checked in the controller cpl as I thought that's the case too, but it responds perfectly fine there with no missed presses

xan1242 commented 2 years ago

I still cannot validate this issue without more samples, so unless more people chime in I can't confirm the problem to exist.

I've compared against PS2 and 360 versions, it acts exactly the same when I mash the start button.

Tested with a variety of gamepads, 360, DS3, DS4, Xbox One... None of them exhibit this issue you're having.

AeroWidescreen commented 2 years ago

I initially had the same problem, for what it's worth. But this was back when I was using one of the early test builds you gave me. I can't reproduce the problem anymore.

Pause

TorutheRedFox commented 2 years ago

I'm getting low framerates because of reshade so it might be an issue related to framerate?

try bogging the game down with a ridiculously overcomplicated reshade shader (idk add a for loop that does nothing for 32 iterations per pixel or smth idk lmao)

I'll do more tests and report back

TorutheRedFox commented 2 years ago

here's my configuration for reference:

[Input]
KeyboardReadingMode = 0 // 0 = async unbuffered, 1 = buffered
ConfineMouse = 0 // confine mouse within the game window
UseWin32Cursor = 0 // use Win32/OS to render the mouse (recommended for performance benefits)
UseCustomCursor = 0 // use the nfs_cursor.cur file as the cursor style
DeadzonePercentLS = 0.24 // set the percentage of deadzone for each stick here (default 24% = 0.24)
DeadzonePercentRS = 0.24
DeadzonePercentLS_P2 = 0.24
DeadzonePercentRS_P2 = 0.24
DeadzonePercent_Shifting = 0.75 // deadzone for shifting digital activation
DeadzonePercent_AnalogStickDigital = 0.50 // deadzone for analog sticks digital activation
DeadzonePercent_AnalogTriggerDigital = 0.12 // same for triggers

[Icons]
ControllerIconMode = 1 // 0 = Xbox One, 1 = PlayStation 4
FirstControlDevice = 1 // 0 = Keyboard, 1 = Controller -- for setting the first state

[Events]
FRONTENDACTION_UP = XINPUT_GAMEPAD_DPAD_UP
FRONTENDACTION_DOWN = XINPUT_GAMEPAD_DPAD_DOWN
FRONTENDACTION_LEFT = XINPUT_GAMEPAD_DPAD_LEFT
FRONTENDACTION_RIGHT = XINPUT_GAMEPAD_DPAD_RIGHT
FRONTENDACTION_UP_ALT = XINPUT_GAMEPAD_LS_UP
FRONTENDACTION_DOWN_ALT = XINPUT_GAMEPAD_LS_DOWN
FRONTENDACTION_LEFT_ALT = XINPUT_GAMEPAD_LS_LEFT
FRONTENDACTION_RIGHT_ALT = XINPUT_GAMEPAD_LS_RIGHT
FRONTENDACTION_ACCEPT = XINPUT_GAMEPAD_A
FRONTENDACTION_CANCEL = XINPUT_GAMEPAD_B
FRONTENDACTION_RUP = XINPUT_GAMEPAD_RS_UP
FRONTENDACTION_RDOWN = XINPUT_GAMEPAD_RS_DOWN
FRONTENDACTION_RLEFT = XINPUT_GAMEPAD_RS_LEFT
FRONTENDACTION_RRIGHT = XINPUT_GAMEPAD_RS_RIGHT
FRONTENDACTION_BUTTON0 = XINPUT_GAMEPAD_RIGHT_SHOULDER
FRONTENDACTION_BUTTON1 = XINPUT_GAMEPAD_LEFT_SHOULDER
FRONTENDACTION_BUTTON2 = XINPUT_GAMEPAD_LEFT_THUMB
FRONTENDACTION_BUTTON3 = XINPUT_GAMEPAD_RIGHT_THUMB
FRONTENDACTION_BUTTON4 = XINPUT_GAMEPAD_X
FRONTENDACTION_BUTTON5 = XINPUT_GAMEPAD_Y
FRONTENDACTION_LTRIGGER = XINPUT_GAMEPAD_LT
FRONTENDACTION_RTRIGGER = XINPUT_GAMEPAD_RT
FRONTENDACTION_START = XINPUT_GAMEPAD_START
// In-Game controls
GAMEACTION_GAS = XINPUT_GAMEPAD_RT
GAMEACTION_BRAKE = XINPUT_GAMEPAD_LT
GAMEACTION_STEERLEFT = XINPUT_GAMEPAD_LS_LEFT
GAMEACTION_STEERRIGHT = XINPUT_GAMEPAD_LS_RIGHT
GAMEACTION_TURNLEFT = XINPUT_GAMEPAD_LS_LEFT
GAMEACTION_TURNRIGHT = XINPUT_GAMEPAD_LS_RIGHT
GAMEACTION_HANDBRAKE = XINPUT_GAMEPAD_A
GAMEACTION_NOS = XINPUT_GAMEPAD_B
GAMEACTION_GAMEBREAKER = XINPUT_GAMEPAD_LEFT_SHOULDER
GAMEACTION_SHIFTUP = XINPUT_GAMEPAD_Y
GAMEACTION_SHIFTDOWN = XINPUT_GAMEPAD_X
GAMEACTION_RESET = XINPUT_GAMEPAD_BACK
HUDACTION_PAUSEREQUEST = XINPUT_GAMEPAD_START
HUDACTION_ENGAGE_EVENT = XINPUT_GAMEPAD_DPAD_UP
HUDACTION_PAD_LEFT = XINPUT_GAMEPAD_DPAD_LEFT
HUDACTION_PAD_DOWN = XINPUT_GAMEPAD_DPAD_DOWN
HUDACTION_PAD_RIGHT = XINPUT_GAMEPAD_DPAD_RIGHT
HUDACTION_SKIPNIS = XINPUT_GAMEPAD_A
HUDACTION_NEXTSONG = XINPUT_GAMEPAD_RIGHT_THUMB
CAMERAACTION_CHANGE = XINPUT_GAMEPAD_RS_DOWN
CAMERAACTION_LOOKBACK = XINPUT_GAMEPAD_RIGHT_SHOULDER
// Debug camera
CAMERAACTION_DEBUG = XINPUT_GAMEPAD_BACK
DEBUGACTION_DROPCAR = XINPUT_GAMEPAD_START
DEBUGACTION_MOVE_FORWARD = XINPUT_GAMEPAD_LS_UP
DEBUGACTION_MOVE_BACK = XINPUT_GAMEPAD_LS_DOWN
DEBUGACTION_MOVE_LEFT = XINPUT_GAMEPAD_LS_LEFT
DEBUGACTION_MOVE_RIGHT = XINPUT_GAMEPAD_LS_RIGHT
DEBUGACTION_MOVE_UP = XINPUT_GAMEPAD_RIGHT_SHOULDER
DEBUGACTION_MOVE_DOWN = XINPUT_GAMEPAD_LEFT_SHOULDER
DEBUGACTION_LOOK_UP = XINPUT_GAMEPAD_RS_DOWN
DEBUGACTION_LOOK_DOWN = XINPUT_GAMEPAD_RS_UP
DEBUGACTION_LOOK_LEFT = XINPUT_GAMEPAD_RS_LEFT
DEBUGACTION_LOOK_RIGHT = XINPUT_GAMEPAD_RS_RIGHT
DEBUGACTION_TURBO = XINPUT_GAMEPAD_LT
DEBUGACTION_SUPER_TURBO = XINPUT_GAMEPAD_RT
DEBUGACTION_LOOK_D_UP = XINPUT_GAMEPAD_DPAD_UP
DEBUGACTION_LOOK_D_DOWN = XINPUT_GAMEPAD_DPAD_DOWN
DEBUGACTION_LOOK_D_LEFT = XINPUT_GAMEPAD_DPAD_LEFT
DEBUGACTION_LOOK_D_RIGHT = XINPUT_GAMEPAD_DPAD_RIGHT
DEBUGACTION_MOVE_D_FORWARD = XINPUT_GAMEPAD_Y
DEBUGACTION_MOVE_D_BACK = XINPUT_GAMEPAD_A
DEBUGACTION_MOVE_D_LEFT = XINPUT_GAMEPAD_X
DEBUGACTION_MOVE_D_RIGHT = XINPUT_GAMEPAD_B
[EventsKB]
FRONTENDACTION_UP = VK_UP
FRONTENDACTION_DOWN = VK_DOWN
FRONTENDACTION_LEFT = VK_LEFT
FRONTENDACTION_RIGHT = VK_RIGHT
FRONTENDACTION_ACCEPT = VK_RETURN
FRONTENDACTION_CANCEL = VK_ESCAPE
FRONTENDACTION_RUP = W
FRONTENDACTION_RDOWN = S
FRONTENDACTION_RLEFT = A
FRONTENDACTION_RRIGHT = D
FRONTENDACTION_BUTTON0 = 3
FRONTENDACTION_BUTTON1 = 5
FRONTENDACTION_BUTTON2 = T
FRONTENDACTION_BUTTON3 = R
FRONTENDACTION_BUTTON4 = 2
FRONTENDACTION_BUTTON5 = 1
FRONTENDACTION_LTRIGGER = 9
FRONTENDACTION_RTRIGGER = 0
FRONTENDACTION_START = 4
// In-Game controls
GAMEACTION_GAS = VK_UP
GAMEACTION_BRAKE = VK_DOWN
GAMEACTION_STEERLEFT = VK_LEFT
GAMEACTION_STEERRIGHT = VK_RIGHT
GAMEACTION_TURNLEFT = VK_LEFT
GAMEACTION_TURNRIGHT = VK_RIGHT
GAMEACTION_HANDBRAKE = VK_SPACE
GAMEACTION_NOS = VK_MENU
GAMEACTION_GAMEBREAKER = X
GAMEACTION_SHIFTUP = VK_LSHIFT
GAMEACTION_SHIFTDOWN = VK_LCONTROL
GAMEACTION_RESET = R
HUDACTION_PAUSEREQUEST = VK_ESCAPE
HUDACTION_ENGAGE_EVENT = VK_RETURN
HUDACTION_PAD_LEFT = M
HUDACTION_PAD_RIGHT = VK_TAB
HUDACTION_PAD_DOWN = B
CAMERAACTION_CHANGE = C
CAMERAACTION_LOOKBACK = L
CAMERAACTION_PULLBACK = P
// Debug camera
CAMERAACTION_DEBUG = VK_SUBTRACT
DEBUGACTION_DROPCAR = 5
DEBUGACTION_MOVE_FORWARD = W
DEBUGACTION_MOVE_BACK = S
DEBUGACTION_MOVE_LEFT = A
DEBUGACTION_MOVE_RIGHT = D
DEBUGACTION_MOVE_UP = VK_SPACE
DEBUGACTION_MOVE_DOWN = VK_LCONTROL
DEBUGACTION_LOOK_UP = K
DEBUGACTION_LOOK_DOWN = I
DEBUGACTION_LOOK_LEFT = J
DEBUGACTION_LOOK_RIGHT = L
DEBUGACTION_TURBO = VK_LSHIFT
DEBUGACTION_SUPER_TURBO = F
xan1242 commented 2 years ago

The device polling runs in the exact same place in the code where the original was (which is within the main thread). It is beefier than the original, yes, but really not by enough to exhibit this behavior.

These issues should (and probably will) happen without this plugin on low framerate in any case (and I can confirm this from running this game on a Pentium 1 MMX). It is standard behavior on low tick/frame rates to behave like this.

The only "solution" that I can think of is to make this multi-threaded which would cause other issues, such as synchronization problems with the game's own queue.

AeroWidescreen commented 2 years ago

Hmm... well I got it to happen to again but it's very inconsistent. When I press the "start" button, sometimes it doesn't register my input. I have to press it twice. But I have no reliable way to reproduce this, it just happens at random apparently.

TorutheRedFox commented 2 years ago

it's really inconsistent and only happens with pausing every other action (reset, shift gears, NOS, handbrake, etc.) works without issue

AeroWidescreen commented 2 years ago

I'm not sure how much this will help, but here's a GIF of it happening.

PauseBug

As you can see, the first input doesn't do anything. I have to press the button again for the start menu to appear. I'm also holding the right trigger down for throttle, so maybe that has something to do with it? I don't know.

When this bug happens, I can hold down the start button for as long as I want and the pause menu will never appear, even after releasing the button.

TorutheRedFox commented 2 years ago

it seems to be much more likely to happen when holding throttle

TorutheRedFox commented 2 years ago

just tested with Carbon and same issue is present there

HarGabt commented 2 years ago

more likely to happen when holding throttle

Throttle has nothing to do with it. As noticed in Aero's gif, it has to do with steering with left stick. I just tried to replicate this issue and it is present. Maybe it has to do with different events that try in some way to overlay one another (turning left and going left/right in pause menu), i dunno.

HarGabt commented 2 years ago

Tested it more and found out the reason that may be closer to truth: if you abuse any analog axis (both of triggers or left stick (with right stick it doesn't work)) by constantly changing its state while trying to pause the game, this makes it impossible to pause the game. Any other event like resetting car with Back button is not affected.

xan1242 commented 2 years ago

Tested it more and found out the reason that may be closer to truth: if you abuse any analog axis (both of triggers or left stick (with right stick it doesn't work)) by constantly changing its state while trying to pause the game, this makes it impossible to pause the game. Any other event like resetting car with Back button is not affected.

That is because car reset isn't a HUD action, but a game action.

The game intercepts FE actions before HUD actions, so if an FE action is activated at the same time as HUD action, it'll only react to the FE action and not the HUD action. This is why I implemented the ignoring of FE actions while the HUD is visible.

That being said... the bug is indeed related to something with analog axis and not with the detection part.

The bug can be replicated reliably by spinning the left stick around constantly and pressing the pause button. HUD actions do not get triggered then.

xan1242 commented 2 years ago

image

Here's an example of a poll when it happens.

For 1 tick only, the HUDACTION_PAUSEREQUEST value is set to 1.0 when the START button is pressed.

As you can see, the polling itself is fine, it's the game misbehaving and not reading it in time. A workaround will be necessary to fix this... I'm not certain of the game's behavior on this but it may just react to changes in the value and not actually care about it being 1 or 0.

xan1242 commented 2 years ago

Also, when I make the polling switch between on/off states every tick, the game slows down the HUD actions drastically only when analog axis keep changing for some reason.

xan1242 commented 2 years ago

Interestingly, unbinding the analog actions makes the HUD actions a lot more reactive...

So as an attempt to fix this, HUD actions will exclusively be polled before anything else

xan1242 commented 2 years ago

It did not make a difference, go figure.

I will not be making any modifications to the polling code then... I can confirm it is fine. The workaround will lie somewhere else in the game code.

xan1242 commented 2 years ago

The action doesn't even seem to reach the queue at all. FEngHud::JoyHandle doesn't even react in time for some reason...

xan1242 commented 2 years ago

OK so the reason for this behavior is because of the way FEngHud::JoyHandle handles the ActionQueue.

Instead of processing each action one by one, it instead just takes 1 and pops the rest of the queue and doesn't care about the rest.

The way I made the plugin generate the input maps affects the way the queue is generated as well, hence a lot of analog actions in the queue could clog it up and make the FEngHud not react in time.

The workaround is to jump right after the ActionQueue::PopAction back to the beginning where it checks ActionQueue::IsEmpty. I hadn't yet noticed any side effects from this so I'll implement it in the next version.

xan1242 commented 2 years ago

fixed with commit 6ad099f

Closing