Konami Code (XInput / Dinput) Topic is solved

Ask gaming related questions (AHK v1.1 and older)
User avatar
elModo7
Posts: 217
Joined: 01 Sep 2017, 02:38
Location: Spain
Contact:

Konami Code (XInput / Dinput)

23 Jan 2018, 16:02

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?
User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

Re: Konami Code (XInput / Dinput)

24 Jan 2018, 08:14

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.

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
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.

Image
User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

Re: Konami Code (XInput / Dinput)

24 Jan 2018, 09:24

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:

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()
			}
		}
	}
}
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.
User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

Re: Konami Code (XInput / Dinput)

24 Jan 2018, 09:56

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.

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
}
User avatar
evilC
Posts: 4822
Joined: 27 Feb 2014, 12:30

Re: Konami Code (XInput / Dinput)

24 Jan 2018, 11:15

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
	}
}
MaxAstro
Posts: 557
Joined: 05 Oct 2016, 13:00

Re: Konami Code (XInput / Dinput)

24 Jan 2018, 15:25

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.
User avatar
elModo7
Posts: 217
Joined: 01 Sep 2017, 02:38
Location: Spain
Contact:

Re: Konami Code (XInput / Dinput)  Topic is solved

09 Feb 2018, 04:37

This is what I ended up doing for the XInput version:

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
Basically the easy way as I'm a noob doing this things.

Return to “Gaming Help (v1)”

Who is online

Users browsing this forum: No registered users and 46 guests