PC Players: Enhancing Training Mode using macro programs

This technique can be applied to many different fighting games on PC, including emulated ones (as long as they’re compatible with AutoIt) but since the script I created was for SSFIV I figured I’d just post it in this forum.

One of the problems with the training mode dummy is that it can only store 10 seconds of input, and because there’s no scripting capability in the game, you’re limited to one setup per recording. This is fine for testing canned stuff, but things get trickier if you want to test maybe something like a defensive technique that covers multiple mixup options.

We can simulate this behaviour with a tool like AutoIt.

AutoIt is a freeware program used for automating input (usually from keyboard or mouse). It’s already widely known and used in the TA combo vid scene.

The idea is to set the dummy to human keyboard control, and run an AutoIt script containing the commands to execute the setup or behaviour you want to playback. The beauty of AutoIt is that it’s completely scriptable, and you can create scripts that simulate unpredictable behaviour by using the Random function.

Here’s a simple script that simulate Ryu walking back and forth a random distance within a defined range and then randomly doing cr.mk. You can playback this script to test your spacing and whiff-punishment against this move.

Install AutoIt and paste the following code into a file called “Ryu test.au3”. Start SSFIV (in a Window) and go to training mode. Set player 2 keys accordingly (you can modify the script so the keys match your own settings). Then set a Ryu dummy to human control and select the keyboard for Player 2 (both players can use the keyboard). Right click on the au3 file and select “Run Script”. Switch back to SSFIV and the Ryu dummy will start walking back and forth, doing cr.mk randomly. Try to space yourself out of range of the move and whiff punish it whenever possible.



; This script assumes that the dummy will always be facing left
 
; Constant values
Const $ONE_FRAME_IN_MS = 16.67
Const $MAX_WALK_FRAMES = 20
Const $MIN_WALK_FRAMES = 5

; Change these to match your own key configuration
Const $LEFT     = "c"
Const $RIGHT     = "b"
Const $UP         = "f"
Const $DOWN     = "v"
Const $MK        = "w"

; The "meat" of the script - it will loop forever
While(true)
   ; Walk left a random amount of time
   $howLong = Random($MIN_WALK_FRAMES, $MAX_WALK_FRAMES, 1) * $ONE_FRAME_IN_MS
   WalkLeft($howLong)
   
   ; Randomly do cr.mk
   If Mod(Round($howLong), 5) = 0 Then
      CrouchMediumKick()
   EndIf
   
   ; Walk backward roughly the same distance, but modify 
   ; the time because Ryu's backward speed is slower
   ; than his forward speed
   $howLong += $ONE_FRAME_IN_MS * 6
   WalkRight($howLong)
   
   ; Randomly do cr.mk
   If Mod(Round($howLong), 4) = 0 Then
      CrouchMediumKick()
   EndIf
WEnd

Func WalkLeft($howLong)
   Send ("{" & $LEFT & " down}")
   Sleep($howLong)   
   Send ("{" & $LEFT & " up}")
EndFunc

Func WalkRight($howLong)
   Send ("{" & $RIGHT & " down}")
   Sleep($howLong)   
   Send ("{" & $RIGHT & " up}")
EndFunc

Func CrouchMediumKick()
   Send ("{" & $LEFT & " down}")
   Send ("{" & $DOWN & " down}")
   Sleep($ONE_FRAME_IN_MS)
   Send ("{" & $MK & " down}")
   Sleep($ONE_FRAME_IN_MS)
   Send ("{" & $MK & " up}")
   Send ("{" & $LEFT & " up}")
   Send ("{" & $DOWN & " up}")
EndFunc


This is just a basic example, but it’s fairly trivial to modify the code to do other stuff, like random fireballs, or whiffing stand short etc. Once you get used to the idea of scripting you can create more complex scripts, like 50/50 mixups, or tick throw/frame traps etc.

If you come up with something cool post it here.

Here’s an example of a script that performs a 50/50 mixup. Ryu does f.throw, backdash, j.hp or x-up tatsu. The way to defend against this is to hold back, then down forward immediately. Holding back protects against the j.hp, while down forward will block the x-up tatsu. It works because the j.hp hits before the tatsu. It’s not foolproof though cos Ryu can change the timing of the tatsu to hit in front.

This script operates differently to the previous one in that the setup will only run when you press “8” or “9”. Press “8” to perform the setup when the dummy is facing left, press “9” when they are facing right. You can press “0” to reload the script (useful for when you want to make changes on the fly).



HotKeySet ("0", "Reload")
HotKeySet ("9", "PerformSetupFacingRight")
HotKeySet ("8", "PerformSetupFacingLeft")
 
; Constant values
Const $ONE_FRAME_IN_MS = 16.67
Const $AUTOIT_PATH = RegRead("HKEY_LOCAL_MACHINE\SOFTWARE\AutoIt v3\AutoIt", "InstallDir")
Const $RELOAD_CMD = '"' & $AUTOIT_PATH & '\AutoIt3.exe "' & ' "' & @ScriptFullPath & '"'
 
; Change these to match your own key configuration
Const $LEFT    = "c"
Const $RIGHT    = "b"
Const $UP        = "f"
Const $DOWN    = "v"
 
Const $LP        = "1"
Const $MP        = "2"
Const $HP        = "3"
Const $LK        = "q"
Const $MK        = "w"
Const $HK        = "e"
 
; Keep the script running forever
While(true)
  Sleep(100)
WEnd
 
Func PerformSetupFacingRight()
  PerformSetup($RIGHT, $LEFT)
EndFunc
 
Func PerformSetupFacingLeft()
  PerformSetup($LEFT, $RIGHT)
EndFunc
 
 
Func PerformSetup($forward, $backward)
  Walk($forward, 80 * $ONE_FRAME_IN_MS)
  Throw($forward)
  Wait(90)
  Dash($backward)
  Wait(28)
  Jump($forward)
  If (Random(0, 1, 1) = 0) Then
      Wait(28)
      HP()
  Else
      Wait(17)
      QC($backward)
      HK()
  EndIf
EndFunc
 
Func HK()
  SingleButtonPress($HK)
EndFunc
 
Func HP()
  SingleButtonPress($HP)
EndFunc
 
Func QC($direction)
  Send ("{" & $DOWN & " down}")
  Sleep($ONE_FRAME_IN_MS)
  Send ("{" & $direction & " down}")
  Sleep($ONE_FRAME_IN_MS)
  Send ("{" & $DOWN & " up}")
  Sleep($ONE_FRAME_IN_MS)
  Send ("{" & $direction & " up}")
EndFunc
 
Func Throw($direction)
  Send ("{" & $direction & " down}")
  Send ("{" & $LP & " down}")
  Send ("{" & $LK & " down}")
  Sleep($ONE_FRAME_IN_MS)
  Send ("{" & $LP & " up}")
  Send ("{" & $LK & " up}")
  Send ("{" & $direction & " up}")
EndFunc
 
Func Walk($direction, $howLong)
  Send ("{" & $direction & " down}")
  Sleep($howLong)
  Send ("{" & $direction & " up}")
EndFunc
 
Func Dash($direction)
  Send ("{" & $direction & " down}")
  Sleep($ONE_FRAME_IN_MS)
  Send ("{" & $direction & " up}")
  Sleep($ONE_FRAME_IN_MS)
  Send ("{" & $direction & " down}")
  Sleep($ONE_FRAME_IN_MS)
  Send ("{" & $direction & " up}")
EndFunc
 
Func Jump($direction)
  Send ("{" & $UP & " down}")
  Send ("{" & $direction & " down}")
  Sleep($ONE_FRAME_IN_MS)
  Send ("{" & $UP & " up}")
  Send ("{" & $direction & " up}")
EndFunc
 
Func SingleButtonPress($button)
  Send ("{" & $button & " down}")
  Sleep($ONE_FRAME_IN_MS)
  Send ("{" & $button & " up}")
EndFunc
 
Func Wait($frames)
  Sleep($frames * $ONE_FRAME_IN_MS)
EndFunc
 
Func Reload()
  Run($RELOAD_CMD)
  Exit 0
EndFunc


Here’s another cool hack: Letting the dummy react to things you do!

eg. If you want to figure out “safe” spacing for projectiles against different characters, you can hack a script that will let the dummy jump immediately (or with some delay to simulate human reactions) as soon as you throw the projectile. The hack is to set the button used for the projectile eg. fireball (punch button in this case) to run the script. So every time you throw a fireball, the dummy will jump over it and attack (or do whatever the script tells it to do). The only snag is that it’s slightly complicated to do something like this using AutoIt because it doesn’t have built-in joystick/gamepad support (I have done it before though). However, you can use a program called AutoHotkey (which does have stick support) to do the same thing!

As a proof-of-concept, I’ve created a modified AutoHotkey version of the footsie script that will cause the dummy to do cr.hk every time you press MK or HK. In practice, this means that every time you whiff MK or HK the dummy will attempt to counter-sweep, just like a skilled opponent will do. You can obviously do stuff like stand on the other side of the screen and whiff moves just to let the dummy go crazy but that’s defeating the purpose.

Here’s what it looks like in practice:
[media=youtube]Twabz8lWYSo[/media]
The reason the dummy wasn’t doing anything at the start of the vid was because I hadn’t started the script yet.

It’s not perfect and it has it’s limitations, but it’s a heck of a lot better than the standard dummy. (Discussion for another time and place: If I can do this with open source tools and a few hours of spare time, why can’t publishers give us decent Tutorials and Training modes?)

AutoHotKey script:

Spoiler

Common functionality:



#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
SetKeyDelay 0, 20
 
; Constant values
ONE_FRAME_IN_MS := 16.67
 
LEFT    := "c"
RIGHT    := "b"
UP        := "f"
DOWN    := "v"
 
LP        := "1"
MP        := "2"
HP        := "3"
LK        := "q"
MK        := "w"
HK        := "e"
 
HP()
{
    global
    SingleButtonPress(HP)
}
 
MK()
{
    global
    SingleButtonPress(MK)
}
 
HK()
{
    global
    SingleButtonPress(HK)
}
 
 
SingleButtonPress(button)
{
    Send %button%
}
 
Walk(direction, howLong)
{
    Global
    Send {%direction% down}
    Sleep howLong * ONE_FRAME_IN_MS
    Send {%direction% up}
}


Main script:



#include common.ahk
 
MIN_WALK_FRAMES := 5
MAX_WALK_FRAMES := 20
 
Loop
{
  ; Walk left a random amount of time
  Random howLong, MIN_WALK_FRAMES, MAX_WALK_FRAMES
  Walk(LEFT, howLong)
 
  ; Randomly do cr.mk
  If (Mod(Round(howLong), 5) = 0) {
      CrouchMediumKick()
  }
     
  ; Walk backward roughly the same distance, but modify
  ; the time because Ryu's backward speed is slower
  ; than his forward speed
  howLong += 6
  Walk(RIGHT, howLong)
 
  ; Randomly do cr.mk
  If (Mod(Round(howLong), 4) = 0) {
      CrouchMediumKick()
  }
 
  Sleep 16
}
 
; Change Joy3 and Joy4 to whatever joystick buttons
; are used for MK and HK
Joy3::
    Sleep 140
    CrouchHardKick()
    Return
   
Joy8::
    Sleep 140
    CrouchHardKick()
    Return
   
CrouchMediumKick()
{
    Global
    Send {%DOWN% down}
    MK()
    Send {%DOWN% up}
}
 
CrouchHardKick()
{
    Global
    Send {%DOWN% down}
    HK()
    Send {%DOWN% up}
}


this is fucking brilliant mate.

Summary and files here : bit.ly/ssfivtool

To be sure, I can work with Autohotkey only for any kind of patterns, blind or reacting to player 1 moves. Or does Autoit still have it’s use ?
I’m trying to figure out how to script a multi buttons press like down forward. It looked easy in Autoit scripts but that’s not the same exactly in AutoHotKey.

You can use either one, but AutoHotKey has built-in joystick/pad support so you don’t have to write User Defined Functions. Both tools have their pros and cons.

Certain programs can be very finnicky about how much delay you need before inputs are registered. Here’s cr.mk xx hadou in AutoHotKey:



LEFT    := "c"
RIGHT    := "b"
UP        := "f"
DOWN    := "v"
 
LP        := "1"
MP        := "2"
HP        := "3"
LK        := "q"
MK        := "w"
HK        := "e"
 
CrMKxxExHadou(direction)
{
    Global
    Send {%DOWN% down}
    Sleep 30
    ; This function is defined elsewhere but it just sends an MK input
    MK()
    Sleep 30
    Send {%direction% down}
    Sleep 30
    Send {%DOWN% up}
    Sleep 30
    Send {%LP% down}
    Send {%HP% down}
    Sleep 30
    Send {%direction% up}
    Send {%LP% up}
    Send {%HP% up}
}


Call it using:



; Facing Left
CrMKxxExHadou(LEFT)
 
; Facing Right
CrMKxxExHadou(RIGHT)


I call Sleep after certain button presses to give the game a moment to recognize that I pressed the buttons. It’s useful to test the script on P1’s side first with input display on so that you can see what inputs are being generated.

  • deleted useless post -

“Global” means you’re gonna access globally declared variables. When to use % signs for variables takes a bit of getting used to. If you use them with Send, then you always use % signs otherwise it’s going to either send the raw data, or it’s going to try to interpret it as a HotKey.
http://www.autohotkey.com/docs/FAQ.htm#percent

Here’s a working 50/50 tatsu script for AutoHotkey (change the key/stick config accordingly):



#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
SetWorkingDir %A_ScriptDir%  ; Ensures a consistent starting directory.
SetKeyDelay 0, 20
 
; Constant values
ONE_FRAME_IN_MS := 16.67
 
LEFT    := "c"
RIGHT    := "b"
UP        := "f"
DOWN    := "v"
 
LP        := "1"
MP        := "2"
HP        := "3"
LK        := "q"
MK        := "w"
HK        := "e"
 
Wait(frames)
{
    Global
    Sleep frames * ONE_FRAME_IN_MS
}
 
PerformSetupFacingRight()
{
    Global
    PerformSetup(RIGHT, LEFT)
}
 
PerformSetupFacingLeft()
{
    Global
    PerformSetup(LEFT, RIGHT)
}
 
PerformSetup(forward, backward)
{
    Global
    Walk(forward, 80)
    Throw(forward)
    Wait(90)
    Dash(backward)
    Wait(28)
    Jump(forward)
    Random, rand, 0, 1
    if (rand = 0)
    {
        Wait(28)
        HP()
    }
    else
    {
        Wait(17)
        QC(backward)
        HK()
    }
}
 
HK()
{
    Global
    SingleButtonPress(HK)
}
 
HP()
{
    Global
    SingleButtonPress(HP)
}
 
QC(direction)
{
    Global
    Send {%DOWN% down}
    Wait(2)
    Send {%direction% down}
    Wait(2)
    Send {%DOWN% up}
    Wait(2)
    Send {%direction% up}
}
 
Throw(direction)
{
    Global
    Send {%direction% down}
    Send {%LP% down}
    Send {%LK% down}
    Wait(2)
    Send {%LP% up}
    Send {%LK% up}
    Send {%direction% up}
}
 
Walk(direction, howLong)
{
    Global
    Send {%direction% down}
    Wait(howLong)
    Send {%direction% up}
}
Dash(direction)
{
    Global
    Send {%direction% down}
    Wait(2)
    Send {%direction% up}
    Wait(2)
    Send {%direction% down}
    Wait(2)
    Send {%direction% up}
}
 
Jump(direction)
{
    Global
    Send {%UP% down}
    Send {%direction% down}
    Wait(2)
    Send {%UP% up}
    Send {%direction% up}
}
 
Sweep()
{
    Global
    SingleButtonPress(HK)
}
 
SingleButtonPress(button)
{
    Global
    Send {%button% down}
    Wait(2)
    Send {%button% up}
}
 
Joy7::
    PerformSetupFacingRight()


You may have to play with the numbers a bit to get it working on your system, but this works fine on mine. For common functionality, I recommend using Sleep over wait as it gives you more control over the timing. AutoHotKey and AutoIt aren’t frame-perfect.

Just found out that Send can be used to input several buttons, on a single line like :
Send {%UP% up}{%RIGHT% up}…
to do a jump in.

Found it !

Nice, that’s very useful to know.

I was wrong with the “+” just queue brackets one after another without spaces do the trick, + and maybe space between those brackets can still be used to input several commands that aren’t simultaneous, like a QCF why not.

edit : Not possible for such move because we need to release each button pressed AND there’s a diagonal needed.
It’s weird that this code



QC(direction)
{
  Global
  Send {%DOWN% down}
  Wait(1)
  Send {%direction% down}
  Wait(1)
  Send {%DOWN% up}
  Wait(1)
  Send {%direction% up}
}


produce a :d: :db: :b: without the need to code the :db: , I don’t get the mechanism behind this behavior.

It’s not 100% accurate I still got a plink sometimes instead of a clean throw and the timing will take a bit of experimentation to figure out. But this will definitely be handy to level up. Something I needed to add in order to avoid the setup to loop 1 extra time was “exit” at the end of the function. No idea why, I’ll repost the final script later when I get time.

Short code for dashes :



Dash(direction)
{
  Global
  Send {%direction% down} + {%direction% up}
  Wait(2)
  Send {%direction% down} + {%direction% up}
}


Holding down + %direction% gives the diagonal. If you don’t get accurate inputs then use a longer delay between button inputs.

  • deleted useless post -

Make sure you included “Global” in the function otherwise it won’t recognize %UP%. I had the same problem.

And now with SetKeyDelay 0, %UP% works and output a “z” correctly.
I love computers…

Yes I put Global all over the place to be sure.

-Deleted Useless post-

Do you know a way to create a STOP button/hotkey ?
I getting better result today with better wait() management but now that I can launch a loop for the dummy I want to be able to stop it and relaunch at will.
So I use my 2 spare buttons on the right to launch Right or Left side loops, but want to use Select button to stop All (not the script as I need to be able to launch the loop again anytime without switching windows).

Got it !



#include common.ahk
 
MIN_WALK_FRAMES := 5
MAX_WALK_FRAMES := 20
 
Footsies(forward, backward)
{
Global
Pause := false
Loop
{
  if Pause
  break
  ; Walk forward a random amount of time
  Random howLong, MIN_WALK_FRAMES, MAX_WALK_FRAMES
  Walk(forward, howLong)
 
  ; Randomly do cr.mk xx Fireball
  If (Mod(Round(howLong), 5) = 0) {
      Wait(6)
      LowMK_xx_HP(forward)
  }
 
  ; Walk backward roughly the same distance, but modify
  ; the time because Ryu's backward speed is slower
  ; than his forward speed
  howLong += 6
  Walk(backward, howLong)
 
  ; Randomly do cr.mk xx Fireball
  If (Mod(Round(howLong), 4) = 0) {
      Wait(6)
      LowMK_xx_HP(forward)
  }
}
}
 
2Joy5::
  Footsies(RIGHT, LEFT)
 
 
2Joy7::
  Footsies(LEFT, RIGHT)
 
; Push Select to stop ALL
2Joy9::
  Pause := true


We can also use "While (Pause = false){…"
instead of loop