Actually, there is a much better solution. EvilC wrote a piece of code specifically for triggering a function with a sequence of keys. His code, including an example, is reproduced below. You don't really need to know how it works entirely; just drop everything except the examples into an otherwise blank ahk file and #include it in your script. Then follow the examples to create sequences. I've added a second example myself to show how a keyboard sequence would work, since his example is a joystick sequence.
Note that sequences you create need to be in the autoexecute section of the code unless you want to have to trigger a function to activate them. Once you have defined a sequence, you can call Start() and Stop() to start or stop watching for it.
Code: Select all
;; EXAMPLE
dpr := new SeqWatcher(Func("DragonPunchRight"), 0, 100) ; Dragon punch keys must be at most 100ms apart by default
dpr.AddKey("pov", new SeqPov("1JoyPov")) ; Add entry for POV
dpr.AddKey("a", new SeqButton("1Joy1")) ; Add entry for Key A
dpr.AddSeq("pov", povRight)
dpr.AddSeq("pov", povCenter)
dpr.AddSeq("pov", povDown)
dpr.AddSeq("pov", povDownRight)
dpr.AddSeq("pov", povRight)
dpr.AddSeq("a", true, 100, 300) ; Timing exception for this button - min 100ms, max 300ms after Right
dpr.Start()
DragonPunchRight()
{
MsgBox % "You did a dragon punch!"
return
}
;; EXAMPLE 2
seq1 := new SeqWatcher(Func("TestSequence"), 0, 100) ; Note that with the default timing, you have to type fast; change 100 to 250 for more lax timing
seq1.AddKey("S", new SeqButton("s")) ; Add entry for keyboard "s"
seq1.AddKey("E", new SeqButton("e")) ; Add entry for keyboard "e"
seq1.AddKey("Q", new SeqButton("q")) ; Add entry for keyboard "q"
seq1.AddSeq("S", true) ; True means check for key pressed
seq1.AddSeq("S", false) ; False means check for key released
seq1.AddSeq("E", true)
seq1.AddSeq("E", false)
seq1.AddSeq("Q", true)
seq1.AddSeq("Q", false)
seq1.Start() ; Start watching for the sequence
TestSequence()
{
MsgBox % "You pressed S, E, Q!"
return
}
;; END EXAMPLE
; All types (Key, Axis, POV) share this common base class
class SeqObj {
__New(keyName){
this.GetKeyStateName := keyName
}
}
; A Button input
class SeqButton extends SeqObj {
__New(keyName){
base.__New(keyName)
this.value := 0 ; Keyboard default ("Unpressed") value is 0
}
}
; An Axis input
class SeqAxis extends SeqObj {
__New(keyName){
base.__New(keyName)
this.value := 50 ; Axis default ("Centered") value is 50
}
}
; A POV input
class SeqPov extends SeqObj {
__New(keyName){
base.__New(keyName) ; POV default ("Centered") value is -1
this.value := -1
}
}
; The Watcher class
class SeqWatcher {
keys := {}
seq := []
max := 0
pos := 1
__New(callback, defaultMinTime := 0, defaultMaxTime := 0){
this.Callback := callback
this.defaultMinTime := defaultMinTime
this.defaultMaxTime := defaultMaxTime
this.WatcherFn := this.WatchKeys.Bind(this)
}
; Add a new key to the watch list
AddKey(keyName, keyObj){
this.Keys[keyName] := keyObj
}
; Add a KNOWN key to the Sequence
AddSeq(keyName, keyValue, minTime := -1, maxTime := -1){
if (!this.keys.HasKey(keyName)){
MsgBox % "Unknown Key name " keyName
ExitApp
}
if (this.max == 0){
; Min/Max Time for first item is always 0
minTime := -1, maxTime := -1
}
minTime := (minTime == -1 ? this.defaultMinTime : minTime)
maxTime := (maxTime == -1 ? this.defaultMaxTime : maxTime)
this.seq.Push({Name: keyName, Value: keyValue, MinTime: minTime, MaxTime: maxTime})
this.max := this.seq.Length()
}
; Start watching
Start(){
this.pos := 1
fn := this.WatcherFn
SetTimer, % fn, 10
}
; Stop watching
Stop(){
fn := this.WatcherFn
SetTimer, % fn, Off
}
; Called repeatedly when watching, to see if the next key in sequence was hit
WatchKeys(){
; Iterate through all the keys we are watching
for i, keyObj in this.keys {
nextSeq := this.seq[this.pos]
nextKey := this.keys[nextSeq.name]
; The KeyName (eg "1Joy1") that we are expecting to see next in the sequence
expectedKeyStateName := nextKey.GetKeyStateName
; The Value for the KeyName that we are expecting to see next in the sequence
expectedKeyStateValue := nextSeq.value
; ToolTip % "NextKey: " expectedKeyStateName ", NextValue: " expectedKeyStateValue ; Uncomment to monitor sequences via tooltip
isWithinMin := 1
isWithinMax := 1
if (this.lastKeyTime == 0){
isWithinMin := 1, isWithinMax := 1
} else {
timeSinceLastKey := A_TickCount - this.lastKeyTime
isWithinMin := ( nextSeq.MinTime == 0 ? true : timeSinceLastKey > nextSeq.MinTime )
isWithinMax := ( nextSeq.MaxTime == 0 ? true : timeSinceLastKey < nextSeq.MaxTime )
}
; The Actual value we saw for this key
value := GetKeyState(keyObj.GetKeyStateName)
; Did this key change?
if (keyObj.value != value){
; This key changed
keyObj.value := value
; Is this key the next in seqence, and is it the value we are expecting?
if (keyObj.GetKeyStateName == expectedKeyStateName && keyObj.value == expectedKeyStateValue && isWithinMin && isWithinMax){
; Yes - move position on
this.pos++
this.lastKeyTime := A_TickCount
} else {
; No - reset position
this.ResetSequence()
}
; Debugging - show current position
;~ ToolTip % "POS: " this.pos
}
; Check if sequence complete
if (this.pos > this.max){
; Reset to beginning of sequence
this.ResetSequence()
; Fire the Callback
this.callback.Call()
}
}
}
ResetSequence(){
this.pos := 1
this.lastKeyTime := 0
}
}