Page 1 of 1

Trigger action by tapping different keys in rapid succession

Posted: 22 Sep 2018, 09:54
by london1888
I'm trying set up a script that does the following:

Double tap 1::action 1
Quickly tap 1 and 2::action 2
Quickly tap 1 and 3::action 3
Quickly tap 1 and 4::action 4
...

Is there a way to do this? I found RapidHotkey() but it seems to only work when tapping the same key multiple times.

Re: Trigger action by tapping different keys in rapid succession

Posted: 23 Sep 2018, 03:20
by buttshark
I would do this by setting '1' as an individual hotkey, and then look for another key press in the near future via getkeystate().

Code: Select all

~1::
t := A_TickCount + 200 ;adjust number to alter how quickly double tapping should occur
while(getkeystate("1", "p"))
	{}
while(A_TickCount < t)
{
	Loop 10
	{
		key := A_Index - 1
		if(getkeystate(key, "p"))
			goto, lbl%key%
	}
}
return

lbl0:
	MsgBox you tapped 1 then 0
return
lbl1:
	MsgBox you tapped 1 then 1
return
lbl2:
	MsgBox you tapped 1 then 2
return
lbl3:
	MsgBox you tapped 1 then 3
return
lbl4:
	MsgBox you tapped 1 then 4
return
lbl5:
	MsgBox you tapped 1 then 5
return
lbl6:
	MsgBox you tapped 1 then 6
return
lbl7:
	MsgBox you tapped 1 then 7
return
lbl8:
	MsgBox you tapped 1 then 8
return
lbl9:
	MsgBox you tapped 1 then 9
return

Re: Trigger action by tapping different keys in rapid succession

Posted: 23 Sep 2018, 04:47
by swagfag
different solution:

Code: Select all

1::
	ToolTip Press next key now!, A_ScreenWidth/2, A_ScreenHeight/2

	SetTimer TriggerTimeout, -200
	waitingForNextKey := true
return

TriggerTimeout:
	ToolTip

	waitingForNextKey := false
return

#If waitingForNextKey
1::
	MsgBox 11
	waitingForNextKey := false
return

2::
	MsgBox 12
	waitingForNextKey := false
return

3::
	MsgBox 13
	waitingForNextKey := false
return

4::
	MsgBox 14
	waitingForNextKey := false
return

Re: Trigger action by tapping different keys in rapid succession

Posted: 23 Sep 2018, 10:03
by london1888
Thank you both for your reply. However, I apologize for not stating my needs more clearly.
I am actually trying to achieve 16 hotkeys through 2-key combinations of 1, 2, 3, 4. In other words, I need 11, 12, 13, 14, 21, 22, 23, 24, ..., 41, 42, 43, 44.
I tried modifying both of your codes but I couldn't get either to work how I want.

First I tried to modify buttshark's code into a function.

Code: Select all

*$1::Shortcut("{F1}1", "{F1}2", "{F1}3", "{F1}4")
*$2::Shortcut("{F1}5", "{F1}6", "{F1}7", "{F1}8")
*$3::Shortcut("{F2}1", "{F2}2", "{F2}3", "{F2}4")
*$4::Shortcut("{F2}5", "{F2}6", "{F2}7", "{F2}8")

Shortcut(ks1, ks2, ks3, ks4, t = 200) {
	CurrentKey := RegExReplace(A_ThisHotKey,"[\*\~\$\#\+\!\^]")
	t := A_TickCount + t
	while(getkeystate(CurrentKey, "p"))
	{}
	while(A_TickCount < t)
	{
		Loop 4
		{
			key := A_Index
			if(getkeystate(key, "p"))
				goto, lbl%key%
		}
	}
	return
	
	lbl1:
		;Send %ks1%
		MsgBox %CurrentKey% %key% lbl1
	return
	lbl2:
		;Send %ks2%
		MsgBox %CurrentKey% %key% lbl2
	return
	lbl3:
		;Send %ks3%
		MsgBox %CurrentKey% %key% lbl3
	return
	lbl4:
		;Send %ks4%
		MsgBox %CurrentKey% %key% lbl4
	return
}
When running this script, only 11, 22, 33, 44 worked.

Next I tried to write out the script manually without using function.

Code: Select all

*$1::
t := A_TickCount + 200
while(getkeystate("1", "p"))
	{}
while(A_TickCount < t)
{
	Loop 4
	{
		key := A_Index
		if(getkeystate(key, "p"))
			goto, lbl1_%key%
	}
}
return

*$2::
t := A_TickCount + 200
while(getkeystate("2", "p"))
	{}
while(A_TickCount < t)
{
	Loop 4
	{
		key := A_Index
		if(getkeystate(key, "p"))
			goto, lbl2_%key%
	}
}
return


lbl1_1:
	MsgBox 11
return
lbl1_2:
	MsgBox 12
return
lbl1_3:
	MsgBox 13
return
lbl1_4:
	MsgBox 14
return
lbl2_1:
	MsgBox 21
return
lbl2_2:
	MsgBox 22
return
lbl2_3:
	MsgBox 23
return
lbl2_4:
	MsgBox 24
return
I only had 11 through 24, and like before 12 and 21 doesn't work. 13, 14, 23, 24 works but I suspect if I wrote in hotkeys for 3 and 4 they will stop working as well.

Then I tried modifying swagfag's code.

Code: Select all

*$1::
	SetTimer 1Timeout, -200
	1Next := true
return

*$2::
	SetTimer 2Timeout, -200
	2Next := true
return

1Timeout:
	1Next := false
return

2Timeout:
	2Next := false
return

#If 1Next
*$1::
	MsgBox 11
	1Next := false
return

*$2::
	MsgBox 12
	1Next := false
return

*$3::
	MsgBox 13
	1Next := false
return

*$4::
	MsgBox 14
	1Next := false
return

#If 2Next
*$1::
	MsgBox 21
	2Next := false
return

*$2::
	MsgBox 22
	2Next := false
return

*$3::
	MsgBox 23
	2Next := false
return

*$4::
	MsgBox 24
	2Next := false
return
This actually works. But I want to use those as shortcuts in a game so I tried to add this in the beginning.

Code: Select all

#IfWinActive ahk_exe MonsterHunterWorld.exe
And suddenly 11, 12, 21, 22 won't work anymore. Same as before 13, 14, 23, 24 still works but if I added the hotkeys for 3 and 4 then everything probably would stop working.

What baffles me is that, if I comment out the #IfWinActive, then the script works perfectly in game. When I replaced the MsgBox with actual game commands, they trigger the in-game actions flawlessly.

Re: Trigger action by tapping different keys in rapid succession

Posted: 23 Sep 2018, 11:26
by swagfag
thats because #If directives overwrite each other

try this instead:

Code: Select all

; these directives need to exist somewhere in the script for Hotkey, If, CustomExpression to work
#If waitForNextKey && WinActive("ahk_exe MonsterHunterWorld.exe")
#If

Keymap := [["{F1}1", "{F1}2", "{F1}3", "{F1}4"]
		 , ["{F1}5", "{F1}6", "{F1}7", "{F1}8"]
		 , ["{F2}1", "{F2}2", "{F2}3", "{F2}4"]
		 , ["{F2}5", "{F2}6", "{F2}7", "{F2}8"]]

for key in Keymap
{
	; define keys that initiate the standby phase
	Hotkey IfWinActive, ahk_exe MonsterHunterWorld.exe
	Hotkey % "*" key, SetTimeout
	Hotkey If
	
	; define keys that send and end the standby phase
	Hotkey If, waitForNextKey && WinActive("ahk_exe MonsterHunterWorld.exe")
	Hotkey % "*" key, SendKey
	Hotkey If
}

SetTimeout:
	ToolTip Press next key now!, A_ScreenWidth/2, A_ScreenHeight/2 ; debug only

	SetTimer TriggerTimeout, -200 ; adjust timeout duration here
	firstKey := stripWildcard(A_ThisHotkey) ; this is to keep track of which key initiated the sending sequence
	waitForNextKey := true ; set the flag and enable the second key layer
Return

TriggerTimeout:
	ToolTip ; debug only
	waitForNextKey := false ; unset the flag and disable the second key layer
	firstKey := "" ; blank out just in case
Return

SendKey:
	Send % Keymap[firstKey, getSecondKey(A_ThisHotkey)] ; pick the send command from the 2d array
	waitForNextKey := false ; unset the flag
Return

stripWildcard(key) {
	return StrReplace(key, "*")
}

getSecondKey(key) {
	return stripWildcard(key)
}

Re: Trigger action by tapping different keys in rapid succession

Posted: 23 Sep 2018, 11:45
by london1888
swagfag wrote:thats because #If directives overwrite each other

try this instead:

Code: Select all

; these directives need to exist somewhere in the script for Hotkey, If, CustomExpression to work
#If waitForNextKey && WinActive("ahk_exe MonsterHunterWorld.exe")
#If

Keymap := [["{F1}1", "{F1}2", "{F1}3", "{F1}4"]
		 , ["{F1}5", "{F1}6", "{F1}7", "{F1}8"]
		 , ["{F2}1", "{F2}2", "{F2}3", "{F2}4"]
		 , ["{F2}5", "{F2}6", "{F2}7", "{F2}8"]]

for key in Keymap
{
	; define keys that initiate the standby phase
	Hotkey IfWinActive, ahk_exe MonsterHunterWorld.exe
	Hotkey % "*" key, SetTimeout
	Hotkey If
	
	; define keys that send and end the standby phase
	Hotkey If, waitForNextKey && WinActive("ahk_exe MonsterHunterWorld.exe")
	Hotkey % "*" key, SendKey
	Hotkey If
}

SetTimeout:
	ToolTip Press next key now!, A_ScreenWidth/2, A_ScreenHeight/2 ; debug only

	SetTimer TriggerTimeout, -200 ; adjust timeout duration here
	firstKey := stripWildcard(A_ThisHotkey) ; this is to keep track of which key initiated the sending sequence
	waitForNextKey := true ; set the flag and enable the second key layer
Return

TriggerTimeout:
	ToolTip ; debug only
	waitForNextKey := false ; unset the flag and disable the second key layer
	firstKey := "" ; blank out just in case
Return

SendKey:
	Send % Keymap[firstKey, getSecondKey(A_ThisHotkey)] ; pick the send command from the 2d array
	waitForNextKey := false ; unset the flag
Return

stripWildcard(key) {
	return StrReplace(key, "*")
}

getSecondKey(key) {
	return stripWildcard(key)
}
Thank you. I tried it but only the the tooltip shows up when I press a key combo. The actual command doesn't seem to trigger.

Re: Trigger action by tapping different keys in rapid succession

Posted: 23 Sep 2018, 13:23
by london1888
swagfag wrote:thats because #If directives overwrite each other

try this instead:

Code: Select all

; these directives need to exist somewhere in the script for Hotkey, If, CustomExpression to work
#If waitForNextKey && WinActive("ahk_exe MonsterHunterWorld.exe")
#If

Keymap := [["{F1}1", "{F1}2", "{F1}3", "{F1}4"]
		 , ["{F1}5", "{F1}6", "{F1}7", "{F1}8"]
		 , ["{F2}1", "{F2}2", "{F2}3", "{F2}4"]
		 , ["{F2}5", "{F2}6", "{F2}7", "{F2}8"]]

for key in Keymap
{
	; define keys that initiate the standby phase
	Hotkey IfWinActive, ahk_exe MonsterHunterWorld.exe
	Hotkey % "*" key, SetTimeout
	Hotkey If
	
	; define keys that send and end the standby phase
	Hotkey If, waitForNextKey && WinActive("ahk_exe MonsterHunterWorld.exe")
	Hotkey % "*" key, SendKey
	Hotkey If
}

SetTimeout:
	ToolTip Press next key now!, A_ScreenWidth/2, A_ScreenHeight/2 ; debug only

	SetTimer TriggerTimeout, -200 ; adjust timeout duration here
	firstKey := stripWildcard(A_ThisHotkey) ; this is to keep track of which key initiated the sending sequence
	waitForNextKey := true ; set the flag and enable the second key layer
Return

TriggerTimeout:
	ToolTip ; debug only
	waitForNextKey := false ; unset the flag and disable the second key layer
	firstKey := "" ; blank out just in case
Return

SendKey:
	Send % Keymap[firstKey, getSecondKey(A_ThisHotkey)] ; pick the send command from the 2d array
	waitForNextKey := false ; unset the flag
Return

stripWildcard(key) {
	return StrReplace(key, "*")
}

getSecondKey(key) {
	return stripWildcard(key)
}
I found the problem. Due to this

Code: Select all

; define keys that initiate the standby phase
	Hotkey IfWinActive, ahk_exe MonsterHunterWorld.exe
	Hotkey % "*" key, SetTimeout
	Hotkey If
as long as the window is active, any input will always be treated as the first key regardless of the value of waitForNextKey. I fixed it simply by swapping the position of the two Hotkey If segments so that it becomes

Code: Select all

for key in Keymap
{
	; define keys that send and end the standby phase
	Hotkey If, waitForNextKey && WinActive("ahk_exe MonsterHunterWorld.exe")
	Hotkey % "*" key, SendKey
	Hotkey If

	; define keys that initiate the standby phase
	Hotkey IfWinActive, ahk_exe MonsterHunterWorld.exe
	Hotkey % "*" key, SetTimeout
	Hotkey If
}
This way it checks if waitForNextKey is true, and if not, then it treats the input as the first key.

This now works perfectly. Thank you so much!