How to Double tap to break a looped behaviour?

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
Kolo
Posts: 39
Joined: 21 Sep 2020, 16:46

How to Double tap to break a looped behaviour?

18 Mar 2024, 19:42

So I press "r" and it sends "a" initially then waits 2s and then if "r" is held or reheld leading into the 2s point or clicked after the 2s point it repeats the initial behaviour. i.e "a...{2s}...a...{2.4s}...a...{2s}..a"

What I would like is for double tapping "r" at 'any point' to automatically keep the loop going indefinitely until I tap "r" again...at which point the behaviour should revert to the above behaviour...and then enable me to double tap at any point again again etc.

I have the two components below, but combining them requires some way to check nonstop in the loop if I doubletapped. Or to break the loop from outside the loop. Not sure how to do that, or if there is a better method

Code: Select all

*r::{ 
    if (A_ThisHotkey = A_PriorHotkey and A_TimeSincePriorHotkey < 250){ 
	;something
	}
	loop{ 
		send "a" 
		sleep 2000
		if not GetKeyState("r", "P"){
			break
		}
	}

}

There is the example code on the setTimer page on how u can setup single or double clicks. But it requires an input delay where it holds for 100-500ms to accept the inputs before spitting anything out. 100-500ms is fine for recognizing a double tap, but I need the initial tap code to work quicker, like normal keypresses do.
https://www.autohotkey.com/docs/v2/lib/SetTimer.htm
niCode
Posts: 295
Joined: 17 Oct 2022, 22:09

Re: How to Double tap to break a looped behaviour?

18 Mar 2024, 22:33

Is this what you want?

Code: Select all

*r:: {
    static toggle := false, SendA := Send.Bind('a')

    if toggle {                             ; if timer is going
        SetTimer(SendA, toggle := 0)        ; turn timer off
    }

    SendA()                                 ; send a
    if KeyWait('r', 'T0.1') {               ; if r is released within 100ms
        if KeyWait('r', 'D T0.1') {         ; and r is pressed again within another 100ms
            SetTimer(SendA, toggle := 2000) ; start timer to send a every 2 seconds
        }
    } else {                                ; is r is held for longer than 100ms
        SetTimer(SendA, toggle := 2000)     ; start timer to send a every 2 seconds
        KeyWait('r')                        ; when r is released
        SetTimer(SendA, toggle := 0)        ; turn off timer
    }
}
bj8tr
Posts: 14
Joined: 23 Feb 2014, 14:02

Re: How to Double tap to break a looped behaviour?

18 Mar 2024, 22:59

This is probably not quite what you're looking for, because starting the sequence with a double-r (and nothing else for at least 2 seconds,) instead of just r also makes it run the "loop" twice, but it's another option. If I hadn't started it before seeing niCode's more elegant example... :D

Code: Select all

#Requires AutoHotkey v2.0

rState := r2State := 0

*r::{
  global rState, r2State
  if (A_ThisHotkey == A_PriorHotkey and A_TimeSincePriorHotkey < 250){
    r2State := 1 ; Tells Funky() r was double-tapped
    ;something
  }
  else if (!rState){ ; First r press in >2 secs
    rState := 1 ; Tells this block not to run unless timer expires and sets rState := 0
    Send "a"
    SetTimer Funky, -2000 ; Sets timer to check (doesn't auto-repeat) if double-r was registered
  }
}
Funky(){
  global rState, r2State
  if (!r2State){ ; Double-r wasn't registered, so we end here
    rState := 0 ; But reset this so the next r press will start things again
    return
  }
  Send "a" ; Double-r was registered before last timer expired, so send again
  r2State := 0 ; Unset the double-r registered flag
  SetTimer , -2000 ; Set the timer again for another double-r check in 2 secs, etc.
  ; if not GetKeyState("r", "P"){
  ;   return
  ; }
}
Kolo
Posts: 39
Joined: 21 Sep 2020, 16:46

Re: How to Double tap to break a looped behaviour?

21 Mar 2024, 20:21

niCode wrote:
18 Mar 2024, 22:33
Is this what you want?

Code: Select all

*r:: {
    static toggle := false, SendA := Send.Bind('a')

    if toggle {                             ; if timer is going
        SetTimer(SendA, toggle := 0)        ; turn timer off
    }

    SendA()                                 ; send a
    if KeyWait('r', 'T0.1') {               ; if r is released within 100ms
        if KeyWait('r', 'D T0.1') {         ; and r is pressed again within another 100ms
            SetTimer(SendA, toggle := 2000) ; start timer to send a every 2 seconds
        }
    } else {                                ; is r is held for longer than 100ms
        SetTimer(SendA, toggle := 2000)     ; start timer to send a every 2 seconds
        KeyWait('r')                        ; when r is released
        SetTimer(SendA, toggle := 0)        ; turn off timer
    }
}
Close. The problem with that tho is I need the timer to respect the 2s intervals so as not to subvert them. Meaning anytime the r button is pressed there need to be a two second "you cannot send the letter 'a' via this method until at least 2s later" deal.

Using your code I cameup with this. I changed it so "r" sends the letter "r" instead of "a", so it only involves 1 key.
The big problem is the initial tap cannot be counted towards the double tap. This can be fixed by always triple tapping....but I want it to 'always' activate on double, and to do this it needs to be able to count the initial start tap as part of the double which it currently can't. In other words this only works if u double tap during that 2s interval, not at the start of it.

Code: Select all

+r::
r::{ ;this will send r every two seconds like the prev sleep version
	static cat:=false 
	static cat_inf:=false
	if not cat{ ;inital interval begins
		suspend 1
		send "r" ;will send the letter r instead of entire script again
		suspend 0
		cat:=true ;prevents spam outside interval, in 2000s it'll turn pressable again
		setTimer timer2000, 2000
		timer2000(){ ;if its put outside we have to switch to global var?
			cat:=false ;r gets repressed if its held leading into this point or pressed again after timer done
			if cat_inf{
				send "r" ;resends the hotkey itself
			}
		}
	}
	else{ ;if u double tap during the loop interval
	    if KeyWait('r', 'T0.1'){ ;if the tap while in loop is released within 100ms
	        if KeyWait('r', 'D T0.1'){ ;if there is another tap within 100ms of that release
	        	cat_inf:=not cat_inf
	        	soundbeep ;indictator double tap worked
	        }
	    } 
	}
}
I also don't understand why this doesn't call r properly:
e::{ send "r"}
Double/triple tapping e should work the same as double tapping r, right? It isn't though. It's sending a normal r that's it
niCode
Posts: 295
Joined: 17 Oct 2022, 22:09

Re: How to Double tap to break a looped behaviour?

22 Mar 2024, 02:53

By default KeyWait uses the key's physical state and e is sending a key which would only change the logical state. The second parameter of KeyWait can make it check logical states of a key.

After going through your updated code and trying to understand it, it seems you want these things:
  • Any physical hotkey presses that call your code can only send r once every 2 seconds
  • Double-tapping your hotkeys will toggle r being logically sent (every 2 seconds or off)
It can be hard to understand exactly what someone wants when you don't understand its purpose so this is my best interpretation of what you want:

Code: Select all

$e::
+r::
$r::MyFunction()


MyFunction() {
    static block := false, toggle := false                  ; keep track of block and timer state
    static SendR := Send.Bind('r')                          ; static reference to func object
    key := SubStr(A_ThisHotkey, -1)                         ; get key name (as long as key is one character)

    if not block {                                          ; if not blocked
        Send('r')                                           ; send r
        block := true                                       ; turn block on
        SetTimer(() => block := false, -2000)               ; turn off block after 2 seconds
    }

    if KeyWait(key, 'T0.1') and KeyWait(key, 'D T0.1') {    ; if key is released within 100ms and pressed within 100ms
        SetTimer(SendR, 2000 * (toggle ^= 1))               ; toggle timer that sends r
        SoundBeep()                                         ; indicator double-tap worked
    }
}
Kolo
Posts: 39
Joined: 21 Sep 2020, 16:46

Re: How to Double tap to break a looped behaviour?

22 Mar 2024, 05:21

Is there any way to get around double setTimer or doubleLoop freezing up? Like when I go to duplicate the key in question to make another for a different key, the same thing keeps happening whether I'm using loops or setTimer.... one of the loops or timers will just freezup 'at some point' for at least one of its interations. I keep reading AHK mimics multithreading so....I try putting them in diffferent scripts and run those at the same time, but I always endup with the freezing up albeit with slightly differnt behaviours. I really have to press keys to get the issue in application settings, but in gaming it's really easy to mimick.

Say ingame if you press {f6} you cast an instant invulnerability state that lasts 5seconds. You can spam this after the 5seconds is done. It lasts 5s and it can only be recast once it is over. Holding the {f6} key ingame it 'sometimes' lets u spam it nonstop....its inconsistent especially when u start moving around and pressing other keys while holding it. So I write a script to make it more consistent. This script does the following:
>it binds f6 to the r key so its easier to press
>it allows the skill to be recast if u hold the key down for the entire 5s regardless of any movement inbetween.
>it allows any tap during the time the skill is active to automatically que up a second cast. I can either tap or tap-hold before the current 5s interval is over and the next 5s will automatically be cast....etc.
This all works fine, but say I have a second key I want to do the exact same thing for {f5} works the same and I want to bind it to "1". Well the moment I put both of these in the same script they might work for a while together in tandem...but eventually one of the timers will outright not proc for its 5s interval and basically be frozen for 5s before I can reproc it again. If I put each of these timers in their own script, one of the timers in whatever script is lower on the que WILL NOT WORK AT ALL....it'll be busted from the start. Ideally I want to get both timers working consistently, or at least not bugging completely for 5s where its frozen in the 'cant use the skill at all for 5s cause the key is bugged' state.

Code: Select all


+r::
r::{
    static cat:=false
    static cat_inf:=false
    if not cat{
        send "{f6}" ;sends skill_1 to be cast
        cat:=true ;prevents spam outside interval, in 5030s it'll turn pressable again
        setTimer timer5000, 5030
        timer2000(){
            cat:=false 
            if cat_inf{
                send "r" ;resends the hotkey itself
                cat_inf:=false
            }
        }
    }
    else if cat{
        cat_inf:=true ;if I tap r again 'at any point' during timer2000 it will que and trigger another skillcast after the 5s is over
    }
}

+1::
1::{
    static cat:=false
    static cat_inf:=false
    if not cat{
        send "{f5}" ;skill_2
        cat:=true
        setTimer timer5000, 5030
        timer2000(){
            cat:=false
            if cat_inf{
                send "1"
                cat_inf:=false
            }
        }
    }
    else if cat{
        cat_inf:=true
    }
}

bj8tr
Posts: 14
Joined: 23 Feb 2014, 14:02

Re: How to Double tap to break a looped behaviour?

22 Mar 2024, 10:18

Only thing I can think for you to try is instead of using repeating timers, try the single-fire route and have your timer function decide whether/how long to set another. Like I did in my sample

Though I doubt it'll help in your case, I find doing it that way sometimes helps me keep track of what's supposed to happen. Good luck in any case!

Oh, yeah, there's also #MaxThreadsPerHotkey to think about

Although I play games, and obviously use AutoHotKey... even to automate things in the game... I do at least determinedly avoid using it for anything except "Quality of Life" assists like moving the map around automatically to zone to a new location, or inventory handling. I doubt it would help if they decide to ban me, but it seems it'd make it less likely they would. But, that's another topic...
niCode
Posts: 295
Joined: 17 Oct 2022, 22:09

Re: How to Double tap to break a looped behaviour?

22 Mar 2024, 14:02

I'm not really sure why you're experiencing those things. A completely separate timer in another script that does different things preventing an unrelated timer from working doesn't make sense, unless AHK is storing timer5000 as some global/universal name that is being shared between scripts. I don't know how to solve your problem exactly, but for fun, you can try the below script to see if it works any better for you.

Code: Select all

Spell1 := Spell('1', '{F5}')
Spell2 := Spell('r', '{F6}')

*1::Spell1.Cast()
*r::Spell2.Cast()


class Spell {
    static cooldown := 5030                                                 ; spell cooldown
    block  := false                                                         ; keep track of block
    toggle := false                                                         ; keep track of timer state

    __New(key_pressed, magic) {
        this.key := key_pressed                                             ; set physical key to check for
        this.CastMagic := (*) => Send('{Blind}' magic)                      ; spell key to send
    }

    Cast() {
        if not this.block {                                                 ; if not blocked
            this.CastMagic()                                                ; cast spell
            this.block := true                                              ; turn block on
            SetTimer(() => this.block := false, -Spell.cooldown)            ; turn off block after 2 seconds
        }

        if KeyWait(this.key, 'T0.1') and KeyWait(this.key, 'D T0.1') {      ; if key is released within 100ms and pressed within 100ms
            SetTimer(this.CastMagic, Spell.cooldown * (this.toggle ^= 1))   ; toggle timer that casts spell
            SoundBeep()                                                     ; indicator double-tap worked
        }
    }
}
Kolo
Posts: 39
Joined: 21 Sep 2020, 16:46

Re: How to Double tap to break a looped behaviour?

22 Mar 2024, 16:49

niCode wrote:
22 Mar 2024, 14:02
I'm not really sure why you're experiencing those things. A completely separate timer in another script that does different things preventing an unrelated timer from working doesn't make sense, unless AHK is storing timer5000 as some global/universal name that is being shared between scripts. I don't know how to solve your problem exactly, but for fun, you can try the below script to see if it works any better for you.

Code: Select all

Spell1 := Spell('1', '{F5}')
Spell2 := Spell('r', '{F6}')

*1::Spell1.Cast()
*r::Spell2.Cast()


class Spell {
    static cooldown := 5030                                                 ; spell cooldown
    block  := false                                                         ; keep track of block
    toggle := false                                                         ; keep track of timer state

    __New(key_pressed, magic) {
        this.key := key_pressed                                             ; set physical key to check for
        this.CastMagic := (*) => Send('{Blind}' magic)                      ; spell key to send
    }

    Cast() {
        if not this.block {                                                 ; if not blocked
            this.CastMagic()                                                ; cast spell
            this.block := true                                              ; turn block on
            SetTimer(() => this.block := false, -Spell.cooldown)            ; turn off block after 2 seconds
        }

        if KeyWait(this.key, 'T0.1') and KeyWait(this.key, 'D T0.1') {      ; if key is released within 100ms and pressed within 100ms
            SetTimer(this.CastMagic, Spell.cooldown * (this.toggle ^= 1))   ; toggle timer that casts spell
            SoundBeep()                                                     ; indicator double-tap worked
        }
    }
}
It's good to see another way to write it, but It freezes up like all the others. I can see this even in a basic text document: Change the output keys f5/f6 to a and b. If you presss just combinations of the inputs 1 and r....u'll see one of them freezup and stop working. I can get this consistently by: tap 1 and then tap r....and then after a brief pause holding down 1....and then holding down r....my output will no longer be combination of 'a' every5s and 'b' every 5s it'll just be 'a'....and I'm not even introducing any other keys into this process, just the two so there should be no keyboard rollover issues....i can make one of the inputs ctrl/shift and no difference either. It's really frustrating. And the code you posted even in a game and just one of the hotkeys active, will freezup when I'm pressing other keys inbetween....which is the issue with all of the setTimer code examples except for the last one I posted it bypasses the problems of that and works like the looped version for some reason.
Kolo
Posts: 39
Joined: 21 Sep 2020, 16:46

Re: How to Double tap to break a looped behaviour?

22 Mar 2024, 16:57

niCode wrote:
22 Mar 2024, 14:02
I'm not really sure why you're experiencing those things. A completely separate timer in another script that does different things preventing an unrelated timer from working doesn't make sense, unless AHK is storing timer5000 as some global/universal name that is being shared between scripts. I don't know how to solve your problem exactly, but for fun, you can try the below script to see if it works any better for you.

Code: Select all

Spell1 := Spell('1', '{F5}')
Spell2 := Spell('r', '{F6}')

*1::Spell1.Cast()
*r::Spell2.Cast()


class Spell {
    static cooldown := 5030                                                 ; spell cooldown
    block  := false                                                         ; keep track of block
    toggle := false                                                         ; keep track of timer state

    __New(key_pressed, magic) {
        this.key := key_pressed                                             ; set physical key to check for
        this.CastMagic := (*) => Send('{Blind}' magic)                      ; spell key to send
    }

    Cast() {
        if not this.block {                                                 ; if not blocked
            this.CastMagic()                                                ; cast spell
            this.block := true                                              ; turn block on
            SetTimer(() => this.block := false, -Spell.cooldown)            ; turn off block after 2 seconds
        }

        if KeyWait(this.key, 'T0.1') and KeyWait(this.key, 'D T0.1') {      ; if key is released within 100ms and pressed within 100ms
            SetTimer(this.CastMagic, Spell.cooldown * (this.toggle ^= 1))   ; toggle timer that casts spell
            SoundBeep()                                                     ; indicator double-tap worked
        }
    }
}
I appreciate the code as its been useful to see other ways of writing these things I never knew about, but It freezes up like all the others. I can see this even in a basic text document: Change the output keys f5/f6 to a and b. If you presss just combinations of the inputs 1 and r....u'll see one of them freezup and stop working. I can get this consistently by: tap 1 and then tap r....and then after a brief pause holding down 1....and then holding down r....my output will no longer be combination of 'a' every5s and 'b' every 5s it'll just be 'a'....and I'm not even introducing any other keys into this process, just the two so there should be no keyboard rollover issues....i can make one of the inputs ctrl/shift and no difference either. It's really frustrating. And the code you posted even in a game and just one of the hotkeys active, will freezup when I'm pressing other keys inbetween....which is the issue with all of the setTimer code examples except for the last one I posted it bypasses the problems of that and works like the looped version for some reason (but the issues with those two codes, AND all the others, is when u have 1 or more hotkeys, one of the setTimers/loops/sleeps break/skips its interval and won't work until 5s after it should have worked). You can play with 5s timer and increase/decrease it but it won't make any difference in a text editor, in a game u can increase it for a laggier but more reliable(on paper) macro but it'll make no difference with the freezing up. And ya I'm jiggling/testing the keys to 'warm them up' so there's no first press errors/lagg after script loads....they just don't function right on the second/third/10th+ attempts.

Is there a multithreading language I need to be looking at instead? Is Autohotkey_H going to change anything? Looked dead and I got a dozen virus warnings downloading the zip for 2.0. Cause again, right now I have the looped version and the previous setTimer version for "ONE hotkey timer/loop" working properly....the idea I can't have two working even in separate running scripts is ridiculous. I can have dozens of hotkeys with codeblocks and switches that don't have loops/timers working fine.
niCode
Posts: 295
Joined: 17 Oct 2022, 22:09

Re: How to Double tap to break a looped behaviour?

22 Mar 2024, 17:14

I can get this consistently by: tap 1 and then tap r....and then after a brief pause holding down 1....and then holding down r....my output will no longer be combination of 'a' every5s and 'b' every 5s
If you're talking about my code, it doesn't toggle the timer on hold or press, it requires a double-tap so the timer never starts in the scenario you're describing.

If you're talking about your previous code, it could be because of the way you've implemented it. Sending a key to trigger another key that triggers some other code is not a good way to do things. Especially if the first key you're sending is the same key, that can cause an infinite loop that creates problems.

You could try changing the Sends to SendEvent. My personal number one rule when coding for games is to do that. Often times games don't poll fast enough to catch all keys sent by SendInput which is the default Send mode. It's possible by pressing other keys, it interferes with what keys the game captures.

I haven't used AutoHotkey_H so I'm not sure if that will make any difference. I personally had no issues with my code (ran it for over a minute while watching a stopwatch) so I'm not sure what the culprit is.
Kolo
Posts: 39
Joined: 21 Sep 2020, 16:46

Re: How to Double tap to break a looped behaviour?

22 Mar 2024, 17:42

niCode wrote:
22 Mar 2024, 17:14
I can get this consistently by: tap 1 and then tap r....and then after a brief pause holding down 1....and then holding down r....my output will no longer be combination of 'a' every5s and 'b' every 5s
If you're talking about my code, it doesn't toggle the timer on hold or press, it requires a double-tap so the timer never starts in the scenario you're describing.

If you're talking about your previous code, it could be because of the way you've implemented it. Sending a key to trigger another key that triggers some other code is not a good way to do things. Especially if the first key you're sending is the same key, that can cause an infinite loop that creates problems.

You could try changing the Sends to SendEvent. My personal number one rule when coding for games is to do that. Often times games don't poll fast enough to catch all keys sent by SendInput which is the default Send mode. It's possible by pressing other keys, it interferes with what keys the game captures.

I haven't used AutoHotkey_H so I'm not sure if that will make any difference. I personally had no issues with my code (ran it for over a minute while watching a stopwatch) so I'm not sure what the culprit is.
That doesn't change anything unfortunately. And none of the other sends work, just send.
I can't even seem to send the control key....how do you send LCTRL?
send "{LCLTR}" not working...
send "^" not working....

I have two version of the same hotkey, presume there is only 'one' running in at any time. Why does the R version work and not the CTRL version?? Anyone know why? Does this have to do with control working different and sending a virtual vs physical state press or something? I don't get it.
And if I put a #HotIf WinActive("ahk_class appnamehere") above the code...it breaks even the working "r" version's ability to tap the key while its on timer and have it retap itself....which I also don't understand why it would do that. It's supposed to as mentioned above...u hit "r"....then it sends {f5} then if u tap r inbetween it should repeat the key in 5s from its original start, it works only when U use the R version and u don't have a hotif....the hotiff breaks that single send line at the end...using lctrl also breaks that single send line.

Code: Select all

;will Never send LCTRL, that one line doesn't work
+LCTRL::
LCTRL::{
    static cat:=false
    static cat_inf:=false
    if not cat{ ;inital interval begins
        send "{f5}" 
        cat:=true 
        setTimer timer, 5030
        timer(){ 
            cat:=false 
            if cat_inf{ 
                send "{LCTRL}" ; this line WILL NOT WORK. supposed to resend the hotkey itself
;i tried send "^" nothing is working, i changed all the ctrl keys to "1"....to "r"..it worked
                cat_inf:=false
            }
        }
    }
    else if cat{
        cat_inf:=true
    }
}

;same as above except it works great, that send line works fine
+r::
r::{
    static cat:=false
    static cat_inf:=false
    if not cat{ ;inital interval begins
        send "{f5}" 
        cat:=true 
        setTimer timer, 5030
        timer(){ 
            cat:=false 
            if cat_inf{ 
                send "r" ; this works fine, its the exact same as the above but ctrl is r
                cat_inf:=false
            }
        }
    }
    else if cat{
        cat_inf:=true
    }
}


Return to “Ask for Help (v2)”

Who is online

Users browsing this forum: No registered users and 99 guests