Multi-threaded key-spamming / rapidfire Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
sh00ter666
Posts: 18
Joined: 07 Oct 2016, 10:55

Multi-threaded key-spamming / rapidfire

12 Oct 2016, 05:16

Hello there.

I have a problem where I set up a AHK script to repeatedly self-send keys when held down according to a guide in the old forums.

When I hold down the F-Key and D-Key, only on of them is sending, meaning that the script can only perform one loop at a time the way it is setup.

What I want to achieve: When holding down Q,W and E for instance. I want all three loops to perform at the same time. How can I achieve this ?

Moreover, if I use modifier keys like SHIFT or CTRL the snippet below does not work. For example if I want to spam F while holding Shift it won't work. I want to send Shift-F-F-F-F-F...

Sample code:

Code: Select all

$d::
        Random, rand, 22,69
    KeyWait d, T0.007              ; Wait 1/2 second for user to release "a" key
    If ErrorLevel                   ; Still held down
        While GetKeyState("d","p")
		{ 
            Send {d down}
			Sleep 45
			Send {d up}
            Sleep rand
        }
    Else                            ; They let go in time
Send {d down}
Sleep 35
Send {d up}
return

$f::
        Random, rand, 11,50
    KeyWait f, T0.007              ; Wait 1/2 second for user to release "a" key
    If ErrorLevel                   ; Still held down
        While GetKeyState("f","p")
		{ 
            Send {f down}
			Sleep 45
			Send {f up}
            Sleep rand
        }
    Else                            ; They let go in time
Send {f down}
Sleep 35
Send {f up}
return
User avatar
sh00ter666
Posts: 18
Joined: 07 Oct 2016, 10:55

Re: Multi-threaded key-spamming / rapidfire

17 Apr 2017, 16:17

Thank you very much for the reply guys! Especially evilC. I have used your method and the script looks something like this now:

Code: Select all

Enter::
Suspend
Sleep 20
Send {Enter down}
Sleep 25
Send {Enter up}
return

; ====================== R B U T T O N =========================
$RButton::
    SetTimer, SendClick, 10
	Gosub, SendClick
return

SendClick:
    Send {RButton down}
	Sleep 25
	Send {RButton up}
return

$RButton up::
	SetTimer, SendClick, Off
return
	
; ======================			 Q    			=========================
$q::
	SetTimer, SendQ, 25
	Gosub, SendQ
	return

$q up::
	Sleep 10
	SetTimer, SendQ, Off
	return

SendQ:
	send {q down}
	Sleep 15
	Send {q up}
return
	
	
	; ======================			 E    			=========================

	
$e::
	SetTimer, SendE, 25
	Gosub, SendE
	return

$e up::
	Sleep 10
	SetTimer, SendE, Off
	return

SendE:
	send {e down}
	Sleep 15
	Send {e up}
	return

;etc... for a couple of  other keys!


1. My only and biggest, pressing issue is that it happens very often that a key becomes 'stuck' and keeps on firing, even though I have released it. I couldn't quite pinpoint what's causing the issue but maybe you know what I am talking about. Possibly a modifier key like SHIFT or CTRL plus the hotkey are triggering this behavior, or if there are multiple actions taking place at once (holding multiple configured buttons).

2. My ENTER hotkey to stop the rapid key spam (so I can type in the in-game chat) also does not seem to work at 100% of the time; if a key happens to be stuck as described, it will keep on spamming until I find out which of the 10 keys it is and I have repressed it.

3. As described in the OP, how would I realize something like sending Shift-E,E,E,E,E,E. If I hold Shift first and then press E down, it works decently. But the other way around won't work, namely hold E down and then Shift. Is there any solution that might be more elegant than going ahead and recreating an additional function for each and every key where you do something like this? (untested):

Code: Select all

!$LBUTTON:: ;ALT+LButton
    SetTimer, clicky, 10
	Gosub, clicky
	return

clicky:
	
	while(GetKeyState("LAlt", "p") && GetKeyState("LBUTTON", "p"))	;not sure how else to check whether both are down
	{
		Send {LButton down} 
		Sleep 15
		Send {LButton up}
	}
SetTimer, clicky, Off
return

Am I looking at a limitation of AHK here because of the threading issue? What if I were to make and run 10 independent scripts with the code from the old example?


Sorry for the late reply, I hope it's okay. And apologies as well for the wall of text, :terms: Please be patient with me :angel:
I feel like this is something that should be perfectly realizable with AHK :HeHe:
Xeno234
Posts: 71
Joined: 24 Mar 2017, 18:14

Re: Multi-threaded key-spamming / rapidfire  Topic is solved

17 Apr 2017, 17:51

Code: Select all

$*lbutton::
$*q::
$*w::
$*e::
	hk := substr(a_thishotkey, 3)
	if hk not in %hks%
	{
		hks .= hks ? "," hk : hk
		settimer label, 30
	}
return
$lbutton up::
$q up::
$w up::
$e up::
	hk := substr(a_thishotkey, 2, strlen(a_thishotkey) - 4)
	hks := RegExReplace(hks, "(,)" hk ",|,{0,1}" hk ",{0,1}", "$1")
	if !hks
		settimer label, off
return
label:
	loop, parse, hks, `,
		sendInput % "{blind}{" A_LoopField "}"
return
The * and blind should solve your number three problem. As for your number one problem, if sendInput works in your game that should solve it, if it doesn't work in your game you might want to try a settimer label2, -15 for the up press, with the assumption that the problem is that sleeps will cause a buildup of delayed hotkeys.
User avatar
sh00ter666
Posts: 18
Joined: 07 Oct 2016, 10:55

Re: Multi-threaded key-spamming / rapidfire

18 Apr 2017, 05:52

Xeno234 wrote: The * and blind should solve your number three problem. As for your number one problem, if sendInput works in your game that should solve it,
Wooow!! You are incredible Xeno234! I thank you very much for taking the time and patience to help me!

The code you provided works like a charm indeed! And it's much cleaner and replaces loads of duplicate code. I will have to read up some parts still to completely grasp it but I can sort of make up what it does.

Unfortunately it still gets stuck in midst of heated battles where it is impossible to track which of the 10 keys are spamming and it's driving me mad :crazy:

I tried playing around with the Settimer delay but it doesn't really help that much, moreover it feels like increasing the timer delay also delays my initial first keystroke which is very bad if I can't just tap the key quickly :sick:

if it doesn't work in your game you might want to try a settimer label2, -15 for the up press, with the assumption that the problem is that sleeps will cause a buildup of delayed hotkeys.
I would love to implement this but I am not quite sure that I understand correctly :?
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Multi-threaded key-spamming / rapidfire

18 Apr 2017, 09:35

If you want to run hotkeys in parallel, AHK_H supports real multi-threading, I have made a plugin for UCR, which uses AHK_H, that lets you just paste your hotkey routine (or any other code) in an edit control and start it in a new thread. Like this,
codethread.png
codethread.png (49.87 KiB) Viewed 5729 times
Good luck.
robx99
Posts: 4
Joined: 18 Apr 2017, 09:15

Re: Multi-threaded key-spamming / rapidfire

18 Apr 2017, 10:00

Hey,

I'm having the same issue as you when releasing a key and AHK not detecting the key up.

https://autohotkey.com/boards/viewtopic.php?f=5&t=30701

In my case, I'm currently using SetTimer, but no luck either.
Xeno234
Posts: 71
Joined: 24 Mar 2017, 18:14

Re: Multi-threaded key-spamming / rapidfire

18 Apr 2017, 10:14

Code: Select all

label:
	loop, parse, hks, `,
	{
		if !getkeystate(A_LoopField, "p") {
			hks := RegExReplace(hks, "(,)" hk ",|,{0,1}" hk ",{0,1}", "$1")
			continue
		}
		sendInput % "{blind}{" A_LoopField "}"
	}
return
Maybe force it to double check.
robx99
Posts: 4
Joined: 18 Apr 2017, 09:15

Re: Multi-threaded key-spamming / rapidfire

18 Apr 2017, 14:45

Nope, neither of those work. I've been using * since the beginning.

The problem lies in AHK not being able to trap the up signal, for some reason, so checking multiple times won't work because it still thinks the key is down. Even KeyWait will remain blocked.

Any ideas?
User avatar
sh00ter666
Posts: 18
Joined: 07 Oct 2016, 10:55

Re: Multi-threaded key-spamming / rapidfire

19 Apr 2017, 14:12

Xeno234 wrote:

Code: Select all

label:
	loop, parse, hks, `,
	{
		if !getkeystate(A_LoopField, "p") {
			hks := RegExReplace(hks, "(,)" hk ",|,{0,1}" hk ",{0,1}", "$1")
			continue
		}
		sendInput % "{blind}{" A_LoopField "}"
	}
return
Maybe force it to double check.
Thank you tons for your suggestion but this created another inexplicable and very unpleasant bug, namely that when I press R for example, then my auto-click on RBUTTON would automatically stop and I couldn't re-enable it without reloading the script :eh:
Edit: actually it also happens with the first version: When I lift one of the letter keys up, then its stop clicking, even though I am still holding RButton down all the time. To resume clicking I need to lift and click and hold again :sick:


@Helgef, that plugin you created looks really crisp! Nice job on that! I am currently writing every hotkey into its own thread. I will see how it goes after some testing and report back!

I guess if all of this doesn't work I will have to try to tinker with something in C# for a while :?
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Multi-threaded key-spamming / rapidfire

19 Apr 2017, 14:39

@sh00ter666, this was meant for you,
Helgef wrote:maybe use * for the up keys.
with reference to Xeno234s code,

Code: Select all

; [...]
$lbutton up::
$q up::
$w up::
$e up::
	; [...]
Cheers.
Unrelated, you do not need $ for mouse buttons, if you have prefixed with *, or suffixed with up, but it doesn't hurt either.
Xeno234
Posts: 71
Joined: 24 Mar 2017, 18:14

Re: Multi-threaded key-spamming / rapidfire

19 Apr 2017, 17:15

sh00ter666 wrote:when I press R for example, then my auto-click on RBUTTON would automatically stop
Should be fixed by adding \b's to the regex.

Code: Select all

$*lbutton::
$*q::
$*l::
$*w::
$*e::
	hk := substr(a_thishotkey, 3)
	if hk not in %hks%
	{
		hks .= hks ? "," hk : hk
		settimer label, 30
	}
return
$lbutton up::
$l up::
$q up::
$w up::
$e up::
	hk := substr(a_thishotkey, 2, strlen(a_thishotkey) - 4)
	hks := RegExReplace(hks, "(,)\b" hk "\b,|,{0,1}\b" hk "\b,{0,1}", "$1")
	if !hks
		settimer label, off
return
label:
	loop, parse, hks, `,
	{
		if !getkeystate(A_LoopField, "p") {
			hks := RegExReplace(hks, "(,)\b" A_LoopField "\b,|,{0,1}\b" A_LoopField "\b,{0,1}", "$1")
			continue
		}
		sendInput % "{blind}{" A_LoopField "}"
	}
return
User avatar
sh00ter666
Posts: 18
Joined: 07 Oct 2016, 10:55

Re: Multi-threaded key-spamming / rapidfire

20 Apr 2017, 01:00

Helgef wrote:@sh00ter666, this was meant for you,
Greetings!
Yeah I didn't miss your message and tested it even though @robx99 mentioned that it didn't work :xmas:
Well... at least this is what I tried:

Code: Select all

; [...]
$*lbutton up::
$*q up::
$*w up::
$*e up::
	; [...]
No improvement :oops:

Xeno234 wrote: Should be fixed by adding \b's to the regex.
Definitely fixed it, thank you so so much Xeno234! :dance:
User avatar
sh00ter666
Posts: 18
Joined: 07 Oct 2016, 10:55

Re: Multi-threaded key-spamming / rapidfire

21 Apr 2017, 04:48

I think the script currently does not allow me to set a time to keep the key down like :

Code: Select all

SendInput {e down}
Sleep 50
SendInput {e up}
Maybe it could solve the problem of keys getting stuck :eh:

But I will need help on editing this segment, since I still don't quite grasp this entire snippet, except for the sendInput bit :oops:

Code: Select all

sendInput % "{blind}{" A_LoopField "}"
Edit:

Nvm, tried something like the following, but it didn't solve the issue either :wtf:

Code: Select all

sendInput % "{blind}{" A_LoopField down "}"
Sleep 50
sendInput % "{blind}{" A_LoopField up"}"
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Multi-threaded key-spamming / rapidfire

21 Apr 2017, 08:32

For a multi-threaded feel, you need to set a timer for the sleeps aswel. As you notice, it gets pretty involved pretty fast. If you don't want to use the real multi-thread that AHK_H offers, which has it owns drawbacks, I would try to make some more general way to handle the timers, for maintainabillity and customisabillity.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Multi-threaded key-spamming / rapidfire

21 Apr 2017, 09:49

This is only briefly tested, and I didn't read you exact requirements to carefully :D
Example in code, some comments, please feel free to ask or ignore.

Code: Select all

; Example:
a:=new spam("$d",["{d down}","sleep 45", "{d up}", "rand 22 69"])
b:=new spam("$f",["{f down}","sleep 45", "{f up}", "rand 11 50"])
c:=new spam("RButton",["{RButton down}","sleep 45", "{RButton up}", "rand 22 69"])
d:=new spam("Space",["abc ","sleep 150", func("f"), "rand 22 69", " def "],"START","STOP")

        
; Turn on/off all hotkeys.
q::spam.turnOffHotkeysForAll()
w::spam.turnOnHotkeysForAll()


f(){
	ToolTip, % A_TickCount
}

class spam {
	; Input:
	;	- Hotkey, any valid ahk key name. 
	;	- seq, an array with either keys to send, a func("functionName") to call, or time to: sleep t, or random sleep time: rand t1 t2 
	static all:=[]
	__new(hotkey,seq,before:="",after:=""){
		this.seq:=seq
		this.hotkey:=hotkey
		this.before:=before
		this.after:=after
		this.timerFn:=ObjBindMethod(this,"do")
		this.bindkey("on",3)
		spam.all.push(this)
	}
	
	turnOffHotkeysForAll(){
		for k, s in spam.all {
			if !s.stopped
				s.stop(1)
			s.turnOffHotkeys()
		}
	}
	turnOnHotkeysForAll(){
		for k, s in spam.all
			s.turnOnHotkeys()
	}
	turnOffHotkeys(){
		this.bindKey("off",3)
		this.bindKey("off",1,1)
		return
	}
	turnOnHotkeys(){
		this.bindKey("on",3)
		return
	}
	bindkey(state,n,fn:=""){
		; Handle hotkeys
		if (n&1) {
			onFn:=ObjBindMethod(this,!fn?"start":"doNothing")
			Hotkey, % this.hotkey, % onFn, % state . " useerrorlevel"
		}
		if (n&2) {
			offFn:=ObjBindMethod(this,"stop")
		 	Hotkey, % (InStr(this.hotkey,"*")?"":"*") . this.hotkey . " up", % offFn, % state . " useerrorlevel"
		}
		return
	}

	doNothing(){	; For suppressing autorepeat.
		return
	}
	start(){	; Start sequence
		this.stopped:=0
		this.do(this.before)
		this.bindkey("off",1)						; Turn off the on key
		this.bindkey("on",1,1)						; suppress autorepeat
		
		
		this.ind:=1
		
		tf:=this.timerFn
		SetTimer, % tf, -0
		return
	}
	
	stop(stopAll:=""){	; Stop the sequence
		
		tf:=this.timerFn
		SetTimer, % tf, off
		this.bindkey("off",1,1)						; Stop suppress autorepeat
		if !stopAll
			this.do(this.after)
		this.stopped:=1
		this.bindkey("on",1)						; Turn on the on key
	}
	
	do(action:=""){	; Perform the next sequence in the sequence array.
		if this.stopped
			return
		if !action
			action:=this.seq[this.ind]
		else
			noTimer:=true
		; This block determines the time for settimer, and perform either send or call action.
		if InStr(action,"sleep") {
			t:=RegExReplace(action,".*?(\d+)","$1")
		} else if InStr(action,"rand"){
			RegExMatch(action,"O)(\d+)\s+(\d+)",t)
			t:=this.rand(t[1],t[2])
		} else if IsObject(action) {
			action.call()
			t:=0
		} else {
			Send, % action
			t:=0
		}
		if noTimer
			return
		this.ind++	; incremenet index
		if (this.ind>this.seq.length())
			this.ind:=1	; wrap index
		tf:=this.timerFn
		SetTimer, % tf, % "-" t
	}
	
	rand(t1,t2){	; For random "sleep" time
		random,rnd,t1,t2
		return rnd
	}
	
}
esc::exitapp
Good luck.

Edit: :oops:

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Google [Bot], Xtra and 128 guests