Whee! this is a fun problem to work on
Here is a version that lets you specify timing constraints for each key in the sequence.
Min = Minimum amount of time that has passed since the last key in the sequence
Max = Maximum amount of time that has passed since the last key in the sequence
You can optionally specify a DEFAULT min and max when you create the watcher:
dpr := new SeqWatcher(Func("DragonPunchRight"), 0, 100) Means "All keys in this sequence have no default Min value and a default Max value of 100ms
Then, when you add a key to the sequence, you can override the defaults:
dpr.AddSeq("a", 100, 300) Means "This key in the Sequence has a Min of 100ms and a Max of 300ms"
You can use -1 to mean "Use Default":
dpr.AddSeq("a", -1, 300) Means "Use the Default Min and a Max of 300ms"
Bear in mind using 0 will override the default with 0:
dpr.AddSeq("a", 0, 300) Means "Use Min of 0 and Max of 300, Regardless of what defaults are"
So, for example, to define a "Dragon Punch" sequence, where right/center/down/downright/right must occur within 100ms of each other (No Min) and the final press of A must occur between 100-300ms after the Right (ie you must wait a bit before pressing A, but not too long), you would use:
Code: Select all
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", 100, 300) ; Timing exception for this button - min 100ms, max 300ms after Right
Here is the new library:
Code: Select all
; 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
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
}
}