EDIT: XInput Done, now on to DInput method
This is driving me mad, can't manage to obtain a Konami Code detection using Joy inputs or XInput.
I mean, I can detect simultaneous button presses on both XInput and DInput, but when it comes to a sequence of buttons like the konami code one (https://i.amz.mshcdn.com/2BrPLT7CBA4q4A ... .056d1.jpg) I really don't know how to make it. Maybe store inputs and then compare somehow?
Konami Code (XInput / Dinput) Topic is solved
Re: Konami Code (XInput / Dinput)
This is quite technically tricky.
I am assuming that Up/Down/Left/Right are DPad (POV Hat) directions.
In AHK, these are read as an ANGLE (POV centered is -1, up is 0, Right is 9000, Down is 18000, etc...)
Also, you need to make sure that any button/direction could potentially "break" the sequence - so if the next thing in the sequence is UP, and we see A get pressed, then we need to break the sequence.
Here is some code which seems to work.
Here is a video of me testing it. I use vJoy as the INPUT, so I can use the "Demo Feeder" app to control the Dpad and buttons using mouse clicks, so you can see what I am doing in the recording.
I am assuming that Up/Down/Left/Right are DPad (POV Hat) directions.
In AHK, these are read as an ANGLE (POV centered is -1, up is 0, Right is 9000, Down is 18000, etc...)
Also, you need to make sure that any button/direction could potentially "break" the sequence - so if the next thing in the sequence is UP, and we see A get pressed, then we need to break the sequence.
Here is some code which seems to work.
Code: Select all
#SingleInstance force
#Persistent
; --- Constants - DO NOT CHANGE ---
povCenter := -1
povUp := 0
povDown := 18000
povLeft := 27000
povRight := 9000
; --- Configurables ---
povKey := "1JoyPOV" ; Controls Which POV is used
keyA := "1Joy1"
keyB := "1Joy2"
; Controls the sequence to watch for
KeySequence := [{Key: povKey, Value: povUp}
, {Key: povKey, Value: povCenter}
, {Key: povKey, Value: povUp}
, {Key: povKey, Value: povCenter}
, {Key: povKey, Value: povDown}
, {Key: povKey, Value: povCenter}
, {Key: povKey, Value: povDown}
, {Key: povKey, Value: povCenter}
, {Key: povKey, Value: povLeft}
, {Key: povKey, Value: povCenter}
, {Key: povKey, Value: povRight}
, {Key: povKey, Value: povCenter}
, {Key: povKey, Value: povLeft}
, {Key: povKey, Value: povCenter}
, {Key: povKey, Value: povRight}
, {Key: povKey, Value: povCenter}
, {Key: keyA, Value: 1}
, {Key: keyA, Value: 0}
, {Key: keyB, Value: 1}
, {Key: keyB, Value: 0}]
; --- End of Configurables ---
; Set initial state of KeyStates array
KeyStates := {}
KeyStates[povKey] := -1
KeyStates[keyA] := 0
KeyStates[keyB] := 0
pos := 1
max := KeySequence.Length()
SetTimer, StickWatcher, 10
return
StickWatcher:
for key, lastValue in KeyStates {
value := GetKeyState(key)
if (KeyStates[key] != value){ ; Input changed state
KeyStates[key] := value ; Update current state
if (KeySequence[pos].value == value){
pos++
ToolTip % "POS: " pos
} else {
pos := 1
}
}
if (pos > max){
MsgBox SEQUENCE TRIGGER
pos := 1
}
}
return
Re: Konami Code (XInput / Dinput)
Actually, this is quite a fun problem to try and solve in a generic, easily configurable manner.
As is usual with me, I did it with the power of Classes:
In theory, this code should let you define any sequence of keyboard, mouse, joystick buttons POVs or Axes!
However with Axes, I doubt it would work properly, as you would have to match every single point the axis passes through, but I included it for completeness, or for devices that eg have digital axes.
As is usual with me, I did it with the power of Classes:
Code: Select all
#SingleInstance force
#Persistent
; Create a new SeqWatcher class...
; ... and tell it to fire the SequenceTrigger() function when the sequence completes
sw := new SeqWatcher(Func("SequenceTrigger"))
; --- Configuration Section START ---
povCenter := -1
povUp := 0
povDown := 18000
povLeft := 27000
povRight := 9000
; Declare the keys that will be watched for this combo
; Give each key a name, plus specify the GetKeyState string to use to check it's value
sw.AddKey("pov", new SeqPov("1JoyPov")) ; Add entry for POV
sw.AddKey("a", new SeqButton("1Joy1")) ; Add entry for Key A
sw.AddKey("b", new SeqButton("1Joy2")) ; Add entry for Key B
; Now define the sequence...
; ...specifying the name parameter, plus the expected value...
; ... for each key in the sequence
sw.AddSeq("pov", povUp)
sw.AddSeq("pov", povCenter)
sw.AddSeq("pov", povUp)
sw.AddSeq("pov", povCenter)
sw.AddSeq("pov", povDown)
sw.AddSeq("pov", povCenter)
sw.AddSeq("pov", povDown)
sw.AddSeq("pov", povCenter)
sw.AddSeq("pov", povLeft)
sw.AddSeq("pov", povCenter)
sw.AddSeq("pov", povRight)
sw.AddSeq("pov", povCenter)
sw.AddSeq("pov", povLeft)
sw.AddSeq("pov", povCenter)
sw.AddSeq("pov", povRight)
sw.AddSeq("pov", povCenter)
sw.AddSeq("a", 1)
sw.AddSeq("a", 0)
sw.AddSeq("b", 1)
sw.AddSeq("b", 0)
; --- Configuration Section END ---
; Start the watcher running
sw.Start()
return
; The function that gets run when the sequence completes
SequenceTrigger(){
MsgBox SEQUENCE COMPLETE
}
; =======================================================================================
; SEQUENCEWATCHER LIBRARY
; 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){
this.Callback := callback
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){
if (!this.keys.HasKey(keyName)){
MsgBox % "Unknown Key name " keyName
ExitApp
}
this.seq.Push({Name: keyName, Value: keyValue})
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 {
; The KeyName (eg "1Joy1") that we are expecting to see next in the sequence
expectedKey := this.keys[this.seq[this.pos].name].GetKeyStateName
; The Value for the KeyName that we are expecting to see next in the sequence
expectedValue := this.seq[this.pos].value
; 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 == expectedKey && keyObj.value == expectedValue){
; Yes - move position on
this.pos++
} else {
; No - reset position
this.pos := 1
}
; Debugging - show current position
ToolTip % "POS: " this.pos
}
; Check if sequence complete
if (this.pos > this.max){
; Reset to beginning of sequence
this.pos := 1
; Fire the Callback
this.callback.Call()
}
}
}
}
However with Axes, I doubt it would work properly, as you would have to match every single point the axis passes through, but I included it for completeness, or for devices that eg have digital axes.
Re: Konami Code (XInput / Dinput)
Also bear in mind that as I have used classes, you can run MULTIPLE SequenceWatchers at the same time.
For example, use this configuration to watch for Street Fighter fireball / dragon punch (Left and Right variants) plus the Konami Sequence.
For example, use this configuration to watch for Street Fighter fireball / dragon punch (Left and Right variants) plus the Konami Sequence.
Code: Select all
povCenter := -1
povUp := 0
povDown := 18000
povDownRight := 13500
povLeft := 27000
povDownLeft := 22500
povRight := 9000
; Konami Sequence --------------------------
ks := new SeqWatcher(Func("KonamiSequence"))
; Declare the keys that will be watched for this combo
; Give each key a name, plus specify the GetKeyState string to use to check it's value
ks.AddKey("pov", new SeqPov("1JoyPov")) ; Add entry for POV
ks.AddKey("a", new SeqButton("1Joy1")) ; Add entry for Key A
ks.AddKey("b", new SeqButton("1Joy2")) ; Add entry for Key B
; Now define the sequence...
; ...specifying the name parameter, plus the expected value...
; For each key in the sequence
ks.AddSeq("pov", povUp)
ks.AddSeq("pov", povCenter)
ks.AddSeq("pov", povUp)
ks.AddSeq("pov", povCenter)
ks.AddSeq("pov", povDown)
ks.AddSeq("pov", povCenter)
ks.AddSeq("pov", povDown)
ks.AddSeq("pov", povCenter)
ks.AddSeq("pov", povLeft)
ks.AddSeq("pov", povCenter)
ks.AddSeq("pov", povRight)
ks.AddSeq("pov", povCenter)
ks.AddSeq("pov", povLeft)
ks.AddSeq("pov", povCenter)
ks.AddSeq("pov", povRight)
ks.AddSeq("pov", povCenter)
ks.AddSeq("a", 1)
ks.AddSeq("a", 0)
ks.AddSeq("b", 1)
ks.AddSeq("b", 0)
; Fireball Right -------------------------------
fbr := new SeqWatcher(Func("FireballRight"))
fbr.AddKey("pov", new SeqPov("1JoyPov")) ; Add entry for POV
fbr.AddKey("a", new SeqButton("1Joy1")) ; Add entry for Key A
fbr.AddSeq("pov", povDown)
fbr.AddSeq("pov", povDownRight)
fbr.AddSeq("pov", povRight)
fbr.AddSeq("a", 1)
; Fireball Left -------------------------------
fbl := new SeqWatcher(Func("FireballLeft"))
fbl.AddKey("pov", new SeqPov("1JoyPov")) ; Add entry for POV
fbl.AddKey("a", new SeqButton("1Joy1")) ; Add entry for Key A
fbl.AddSeq("pov", povDown)
fbr.AddSeq("pov", povDownLeft)
fbl.AddSeq("pov", povLeft)
fbl.AddSeq("a", 1)
; Dragon Punch Right --------------------------
dpr := new SeqWatcher(Func("DragonPunchRight"))
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", 1)
; Dragon Punch Left ----------------------------
dpl := new SeqWatcher(Func("DragonPunchLeft"))
dpl.AddKey("pov", new SeqPov("1JoyPov")) ; Add entry for POV
dpl.AddKey("a", new SeqButton("1Joy1")) ; Add entry for Key A
dpl.AddSeq("pov", povRight)
dpl.AddSeq("pov", povCenter)
dpl.AddSeq("pov", povDown)
dpl.AddSeq("pov", povDownRight)
dpl.AddSeq("pov", povRight)
dpl.AddSeq("a", 1)
; --- Configuration Section END ---
; Start the watchers running
ks.Start()
fbr.Start()
fbl.Start()
dpr.Start()
dpl.Start()
return
; The functions that gets run when the sequence completes
KonamiSequence(){
MsgBox KONAMI SEQUENCE
}
FireballRight(){
MsgBox FIREBALL - RIGHT
}
FireballLeft(){
MsgBox FIREBALL - LEFT
}
DragonPunchRight(){
MsgBox DRAGON PUNCH - RIGHT
}
DragonPunchLeft(){
MsgBox DRAGON PUNCH - Left
}
Re: Konami Code (XInput / Dinput)
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:
Here is the new library:
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
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
}
}
Re: Konami Code (XInput / Dinput)
Wow, I'm glad I happened to watch this thread out of curiosity. This is actually really useful to something I am currently working on.
Re: Konami Code (XInput / Dinput) Topic is solved
This is what I ended up doing for the XInput version:
Basically the easy way as I'm a noob doing this things.
Code: Select all
#Include XInput.ahk
#Persistent
#SingleInstance, Force
#NoTrayIcon
Timer := 100
; Start and Select at the same time, will simulate an Esc press
XInput_Init()
Loop {
Loop, 4 {
if State := XInput_GetState(A_Index-1) {
if(State.wButtons == 1){
CheckSequence("U")
Sleep, Timer
}
if(State.wButtons == 2){
CheckSequence("D")
Sleep, Timer
}
if(State.wButtons == 4){
CheckSequence("L")
Sleep, Timer
}
if(State.wButtons == 8){
CheckSequence("R")
Sleep, Timer
}
if(State.wButtons == 4096){
CheckSequence("B")
Sleep, Timer
}
if(State.wButtons == 8192){
CheckSequence("A")
Sleep, Timer
}
if(State.sThumbLY <= -30768){
CheckSequence("D")
Sleep, Timer
}
if(State.sThumbLY >= 30768){
CheckSequence("U")
Sleep, Timer
}
if(State.sThumbLX <= -30768){
CheckSequence("L")
Sleep, Timer
}
if(State.sThumbLX >= 30768){
CheckSequence("R")
Sleep, Timer
}
}
}
Sleep, 30
}
;-----------------------------------
CheckSequence(Char) {
static Sequence := "" , Wanted := "UUDDLRLRBA"
Sequence := SubStr( Sequence Char , (StrLen(Wanted)-1)*(-1) )
If ( Sequence = Wanted ) ; single '=' should be case insensitive
GoSub, myActionLabel ; you don't have to edit this function everytime
}
;-----------------------------------
myActionLabel:
Run, https://www.youtube.com/channel/UC1O3L1-qKZYyBhyB6_do5-A
Return
Who is online
Users browsing this forum: ReyAHK and 91 guests