I just wrote a Flight Stick/HOTAS AutoHotKey (AHK) Script for Empyrion

Discussion in 'General Discussion' started by Snowmobeetle, Jul 14, 2020.

  1. Snowmobeetle

    Snowmobeetle Ensign

    Joined:
    Jul 14, 2020
    Messages:
    11
    Likes Received:
    20
    EDIT: I Added a feature to switch between Profile1 and Profile2 with the "-" key (changeable). Currently this just supports changing the XYZ axes assignment and Y inversion settings on the fly. I also corrected a small bug in the xy code (it was reading below deadzone unintentionally before sometimes)

    EDIT 2: I cleaned up the binding section a LOT so it's WAY easier to understand



    I've just started playing Empyrion and have enjoyed it a lot! I thought it was a real shame that it didn't support a flight stick, so I decided to do some digging and see if I could make an attempt at a good simple AHK solution. I got carried away and ended up spending most of a day on this (I'm a programmer, but not that familiar with AHK).

    Link to my AHK Empyrion Joystick script (or copy the code below yourself): https://drive.google.com/file/d/1dMsv_uJYdn-ZsnwvAE5bTVtVd4ATwVbO/view?usp=sharing

    Credits:
    I borrowed heavily from these two sources, but did a lot of searching around as well:
    https://www.autohotkey.com/boards/viewtopic.php?t=13117
    https://www.autohotkey.com/docs/misc/RemapJoystick.htm

    I totally rewrote the code that handles the analog inputs. It supports the stick XYZ and Thrust analog using mouse dll calls or varied-length button pulses.



    Notes on Thrust:

    - It does work analog!, but doesn't work while using menus and blocks alt-tabbing out of program

    - I added a bind for a button to enable/disable the thrust control on the fly which makes this very usable and fun - flick it on when you want fine control, flick it off when you want to cruise and use a menu

    - edit: I just verified that you can hold control for a second at full HOTAS throttle & speed, then hit the disable throttle key, and it keeps max speed held (script switches to a solid press at close to max throttle position, won't work at in-between speeds)

    - I added an option to totally disable the thrust control system if you want to use your own system for thrust control



    Other notes/features:

    - Supresses joystick binds unless empyrion window is active window (this can be disabled in settings for testing or use in other games) to prevent it from pressing keys, etc., on you when you're not in-game

    - keep pilot mode turned off in your ship settings for the script to work (it uses mouse for pitch/yaw control)

    - read through options at top of script, there is a lot you can customize

    - you can choose to bind all of the normal Joy1-Joy32 buttons in the script.

    - the #SingleInstance means you can reload the script and it will replace itself. If you update a binding or setting, you can just double click the script again after saving, you don't need to close the one that's already running (as long as the filename remains the same).

    - the analog emulation for thrust and yaw work as follows:
    --- A loop runs on a fixed modulation interval (50ms or so)
    --- The button in question is pressed down at the beginning of each interval
    --- The button is released earlier or later during that interval based on analog input
    --- Experimenting with the modulation time (20-100ms) might improve experience

    - For Roll, Ptich, Yaw, and Pitch Inversion, there are two Profiles to switch between (Thanks to Bollen for the suggestion)
    --- The default for Profile1: Y pitch, X roll, Z yaw, Y inverted
    --- The default for Profile2: Y pitch, X yaw, Z roll, Y non-inverted
    --- These 4 settings are customizable for each profile in the script variables at the beginning of the script
    --- The default key to switch profiles is: "-"



    INSTRUCTIONS TO USE:

    --- install AutoHotKey - https://www.autohotkey.com/ (autohotkey doesn't have it's own ui, just a help file - you won't 'open' the AutoHotKey application)

    --- copy the code I wrote into a text editor (notepad is fine), and save as Something.AHK
    --- OR
    --- download the script here: https://drive.google.com/file/d/1dMsv_uJYdn-ZsnwvAE5bTVtVd4ATwVbO/view?usp=sharing

    --- double click on the file



    The default controls in the script are:

    Profile 1:
    - Joystick X: roll
    - Joystick Y: inverted pitch
    - Joystick Z: yaw (what the mouse does by moving right, turn the ship right like a car)

    Profile 2:
    - Joystick X: roll
    - Joystick Y: pitch
    - Joystick Z: yaw

    Universal bindings (apply for both profiles):
    - Profile switch button: -
    - Hat left: a (strafe left)
    - Hat right: d (strafe right)
    - Hat Up: space (lift)
    - Hat Down: c (lower)
    - Joy1 (trigger): Left Mouse Click
    - Joy2 (thumb button): the letter o (levels ship)
    - Joy3 through Joy8 correspond to 1 - 6 keys, which select weapons/sensors
    - Joy Throttle: w and s keys
    - Joy9 is bound to the in-script feature that toggles the throttle system on and off. You'll need it, because the throttle can't work if you go into a menu or want to switch to windows. You can change the binding to a different joystick button or a keyboard key (or mouse button) if you want.



    The actual code: copy it all and put it in a *.ahk text file:

    Code:
    #SingleInstance force
    #Persistent
    
    ;  FLIGHT STICK/HOTAS MAPPING SCRIPT FOR EMPYRION - GALACTIC SURVIVAL (v12 tested)
    
    ; This is an AutoHotKey script, and requires that (free) application to run
    ; Save this code in a text file and give it the extension *.AHK, then double-click it after instaling AutoHotKey
    
    applicationname=EmpyrionGalacticFlightStickScript
    
    ; === Edit these vars to set up the script to your liking ===
    
    SupressJoystickOutputAwayFromGame := 1                        ; 1 for supress, 0 for don't supress (useful for testing)
    GameWindowName := "Empyrion - Galactic Survival"            ; Supresses joystick output unless this is the active window
    
    InputStick := 1                                                ; The ID of your input stick
    
    ; Assignments of Axes from joystick inputs
    ;   Options are:
    ;        Joystick X Axis:             JoyX
    ;        Joystick Y Axis:             JoyY
    ;        Joystick Z Axis:             JoyR   ;R and Z are backwards because AHK has them reversed
    ;        Joystick Throttle Axis:     JoyZ
    
    ; Profile 1
    YawInputAxisProfile1 := "JoyR"                              
    PitchInputAxisProfile1 := "JoyY"                          
    RollInputAxisProfile1 := "JoyX"                                  
    ThrottleInputAxisProfile1 := "JoyZ"                              
    PitchInvertedProfile1 := 1
    
    
    ; Profile 2  currently set up to switch roll and yaw (leaning stick side to side turns instead of rolls) and does not invert the joystick
    YawInputAxisProfile2 := "JoyX"                          
    PitchInputAxisProfile2 := "JoyY"                      
    RollInputAxisProfile2 := "JoyR"                          
    ThrottleInputAxisProfile2 := "JoyZ"
    PitchInvertedProfile2 := 0
    
    ; Button That Switches Between Profile 1 and Profile 2 Axis Assignments
    ProfileToggleKey := "-"                                  
    
    
    ; Dead Zone adjustments (prevents drift when an analog axis is centered but still sending a tiny signal)
    PitchDeadZone := 15                                            ; Percentage of deadzones per axis
    RollDeadZone := 15
    YawDeadZone := 20
    ThrottleDeadZone := 30
    
    ; Sensitivity Adjustments.  Use 0.1 to 2 or so. will depend on your preferred mouse sensitivity in-game.
    RollSensitivity := 0.2                                  
    PitchSensitivity := 0.2
    YawSensitivity := 0.5                                  
    
    RollAxisOutputKeys := ["q", "e"]                            ; Array of keys to map to the Roll Axis
    ThrottleOutputKeys := ["w","s"]                                ; Array of keys to map to the Throttle axis
    
    ; Assign a button that turns the thrust control on and off in-game
    ThrottleControlDisableEnableKey := "1Joy9"                    ; Assign a button or key that will enable/disable the throttle control in-game (makes the analog control more friendly - you don't have to center it to use menus or switch windows)
                                                                ; If you Assign a Joystick Button Here, put the number of the joystick, then "Joy", then the button number: 1Joy9 for Joystick 1 button 9 (joystick 1 is default)
                                                          
    ; Set this to 0 to disable throttle function entirely                                                      
    EnableThrottleBinding := 1                                    ; Throttle works well, but can't work when in menus and will prevent Alt+Tab to switch apps in windows... (due to empyrion limitations)
    
    HatKeysX := ["a","d"]                                        ; Array of keys to map to the POV hat X axis
    HatKeysY := ["space","c"]                                    ; Array of keys to map to the POV hat Y axis
    
                            ; NOTES ON HOW TO CHANGE KEYBINDINGS:
    Joy1 := "LButton"        ;     LButton, MButton, RButton are the middle, left, and right mouse buttons
    Joy2 := "o"                ;     enter lowercase letters or numbers in the quotes for binds
    Joy3 := "1"                ;     Space, Tab, Enter, Escape, LShift, RShift, LAlt, RAlt, LControl, RControl, F1, F2, ...,  are other common keynames for keys
    Joy4 := "2"                ;     use a carat before a key to bind a control+keypress:      ^p     would bind Ctrl+p
    Joy5 := "3"                ;   use a plus before a key to bind a Shift+keypress:      +p     would bind Shift+p
    Joy6 := "4"                ;   use an exclamation mark before a key for Alt+keypress:    !p    would bind Alt+p
    Joy7 := "5"          
    Joy8 := "6"                ; For more info on what to put in for keys, visit: https://www.autohotkey.com/docs/KeyList.htm
    Joy9 := ""
    Joy10 := ""                ; A button with nothing defined ("") will be skipped and not bound
    Joy11 := ""                ;    - do this to choose not to bind the button or if you're using elsewhere (another script or for the ThrottleControlDisableEnableKey or ProfileToggleKey in this script)
    Joy12 := ""
    Joy13 := ""
    Joy14 := ""
    Joy15 := ""
    Joy16 := ""
    Joy17 := ""
    Joy18 := ""
    Joy19 := ""
    Joy20 := ""
    Joy21 := ""
    Joy22 := ""
    Joy23 := ""
    Joy24 := ""
    Joy25 := ""
    Joy26 := ""
    Joy27 := ""
    Joy28 := ""
    Joy29 := ""
    Joy30 := ""
    Joy31 := ""
    Joy32 := ""
    
    ; These adjust how long the pulse interval is for analog conversions of Yaw and Thrust.  Leave alone unless you're tinkering with the performance of this function
    AxisSendModulationTimeZ := 50                                  ; total time of keypress pulse loops for Roll (the key will be pressed down a fraction of this time depending on analog input
    AxisSendModulationTimeT := 50                              ; total time of keypress pulse loops for Thrust (the key will be pressed down a fraction of this time depending on analog input                  
    minimumSendDurationRatioT := 0.40                             ; Minimum ratio of full that Throttle starts on (lower values weren't working in empyrion, glitching)
                                                          
    ;====== End vars intended to be modified =======
    
    
    
    
    InvertY := PitchInvertedProfile1                            ; assigning profile 1 bindings do not change these               
    YawInputAxis := InputStick YawInputAxisProfile1
    PitchInputAxis := InputStick PitchInputAxisProfile1                      
    RollInputAxis := InputStick RollInputAxisProfile1                      
    ThrottleInputAxis := InputStick ThrottleInputAxisProfile1
    
    Hotkey, %ThrottleControlDisableEnableKey%, ToggleThrottle    ; linking hotkey to function for this binding
    Hotkey, %ProfileToggleKey%, ToggleProfile                    ; linking hotkey to function for this binding
    ThrottleControlEnabled := 1      ; tracks whether user has disabled or enabled throttle control
    CurrentControlProfile := 1      ; tracks whether user is currently on control Profile 1 or 2 (don't change this)
    ThrottleKeyState := 0          ; internal program use
    RollKeyState := 0                ; internal program use
    PitchDeadZone := PitchDeadZone/2                            ; convert deadzone percentages to raw numbers (0-50 range)
    RollDeadZone := RollDeadZone/2
    YawDeadZone := YawDeadZone/2
    ThrottleDeadZone := ThrottleDeadZone/2
    
    if (HatKeysX.length() && HatKeysY.length()){
        HatKeys := []
        HatKeys[1] := [{down: "{" HatKeysX[1] " down}", up: "{" HatKeysX[1] " up}"},{down: "{" HatKeysX[2] " down}", up: "{" HatKeysX[2] " up}"}]
        HatKeys[2] := [{down: "{" HatKeysY[1] " down}", up: "{" HatKeysY[1] " up}"},{down: "{" HatKeysY[2] " down}", up: "{" HatKeysY[2] " up}"}]
        HatEnabled := 1
    } else {
        HatEnabled := 0
    }
    HatState := [0,0]                                            ; The current state of the hat X and Y axes
    ; Build an "Associative Array" map of hat angles to X and Y directions
    HatMap := {-1: [0,0], 0: [0,1], 4500: [2,1], 9000: [2,0], 13500: [2,2], 18000: [0,2], 22500: [1,2], 27000: [1,0], 31500: [1,1]}
    ; Pre-assemble GetKeyState strings for performance optimization
    JoyString := InputStick "Joy"
    AxisStrings := []
    HatString := JoyString "POV"
    Loop 2 {
        AxisStrings.push(JoyString Axes[A_Index])
    }
    
    
    
    
    ; Bind Normal Joystick Buttons 1-32
    
    
    ButtonStrings := []
    ButtonKeyStrings := []
    Loop 32
    {
        currJoyButton := % "Joy" A_Index
        currJoyButtonTarget := %currJoyButton%
        ButtonStrings[A_Index] := JoyString A_Index
        ButtonKeyStrings[A_Index] := {down: "{" currJoyButtonTarget " down}", up: "{" currJoyButtonTarget " up}"}
    }
    
    ;Bind Buttons
    Loop 32
    {
        currJoyButton := % "Joy" A_Index
        currJoyButtonTarget := %currJoyButton%
        if (currJoyButtonTarget != "")
        {
            fn := Func("ButtonPressed").Bind(A_Index)
            hotkey, % JoyString A_Index, % fn
        }
    }
    
    ; start functions that loop and handle axis inputs
    SetTimer, WatchHat, 5
    SetTimer, WatchTwoAxesForMouseOutput, 5
    SetTimer, WatchRoll, 5
    if (EnableThrottleBinding = 1)
        SetTimer, WatchThrottle, 5
     
    return
    
    WatchHat:
        if (!WinActive(GameWindowName) && SupressJoystickOutputAwayFromGame = 1)
            return
    
        ; === Hat ===
        if (!HatEnabled)
            return
        state := GetKeyState(HatString)
        ; Process Hat X axis then Y Axis
        Loop 2 {
            new_state := HatMap[state,A_Index]
            old_state := HatState[A_Index]
            if (old_state != new_state){
                if (old_state)
                    Send % HatKeys[A_Index, old_state].up
                if (new_state)
                    Send % HatKeys[A_Index, new_state].down
                HatState[A_Index] := new_state
            }
        }
     
        return
     
     
    WatchTwoAxesForMouseOutput:
        if (!WinActive(GameWindowName) && SupressJoystickOutputAwayFromGame = 1)
            return
    
        mouseAxisX := GetKeyState(YawInputAxis)-50  ; Get position of axis assigned for X, centered.
        MouseAxisY := GetKeyState(PitchInputAxis)-50  ; Get position of axis assigned for Y, centered.
    
        if (abs(mouseAxisX) > YawDeadZone || abs(MouseAxisY) > PitchDeadZone)
        {
    
            if (InvertY = 1)
                MouseAxisY := -1 * MouseAxisY
    
            ;//factor in deadzone and scale back up
            if (mouseAxisX > YawDeadZone)
                mouseAxisX := (mouseAxisX - YawDeadZone) * 50 / (50-YawDeadZone)
            else if (mouseAxisX < 0 - YawDeadZone)
                mouseAxisX := (mouseAxisX + YawDeadZone) * 50 / (50-YawDeadZone)
            else
                mouseAxisX := 0
      
            if (MouseAxisY > PitchDeadZone)
                MouseAxisY := (MouseAxisY - PitchDeadZone) * 50 / (50-PitchDeadZone)
            else if (MouseAxisY < 0 - PitchDeadZone)
                MouseAxisY := (MouseAxisY + PitchDeadZone) * 50 / (50-PitchDeadZone)
            else
                MouseAxisY := 0
          
            DllCall("mouse_event", uint, 1, int, mouseAxisX * YawSensitivity, int, MouseAxisY * PitchSensitivity)
        }
    
        return
     
    WatchRoll:
        KeyToHoldDownRollR := RollAxisOutputKeys[2]
        KeyToHoldDownRollL := RollAxisOutputKeys[1]
        if (!WinActive(GameWindowName) && SupressJoystickOutputAwayFromGame = 1)
        {
            if (RollKeyState = 1)
            {
                Send, {%KeyToHoldDownRollR% up}
                Send, {%KeyToHoldDownRollL% up}
                RollKeyState := 0
            }
            return
        }
      
        RollAxis := GetKeyState(RollInputAxis)  ; Get position of assigned roll axis.
     
        if (RollAxis > 50 + RollDeadZone)
            KeyToHoldDownRoll := RollAxisOutputKeys[2]      
        else if (RollAxis < 50 - RollDeadZone)
            KeyToHoldDownRoll := RollAxisOutputKeys[1]
        else
        {
            if (RollKeyState = 1)
            {
                Send, {%KeyToHoldDownRollR% up}
                Send, {%KeyToHoldDownRollL% up}
                RollKeyState := 0
            }
            return
        }
     
        SetKeyDelay -1  ; Avoid delays between keystrokes.
     
        if (RollAxis > 90 || RollAxis <10)  ; just holds key down when at max versus pulsing it
        {
            RollKeyState := 1
            Send, {%KeyToHoldDownRoll% down}
            return
        }
    
        possibleZ := 50 - RollDeadZone
        rawAmountZ := abs(RollAxis - 50) ; Range is now -50 to +50
        correctedAmountZ := rawAmountZ - RollDeadZone
     
        Send, {%KeyToHoldDownRoll% down}  ; Press it down.
        Sleep, (correctedAmountZ/possibleZ) * AxisSendModulationTimeZ * RollSensitivity
        Send, {%KeyToHoldDownRoll% up}  ; Release it.
        Sleep, (1- correctedAmountZ/possibleZ) * AxisSendModulationTimeZ * RollSensitivity
    
        return
     
    ToggleThrottle:
        if (ThrottleControlEnabled = 1)
            ThrottleControlEnabled := 0
        else
            ThrottleControlEnabled := 1
        return
     
    ToggleProfile:
        if (CurrentControlProfile = 1)
        {
            YawInputAxis := InputStick YawInputAxisProfile2
            InvertY := PitchInvertedProfile2
            PitchInputAxis := InputStick PitchInputAxisProfile2                      
            RollInputAxis := InputStick RollInputAxisProfile2                      
            ThrottleInputAxis := InputStick ThrottleInputAxisProfile2
            CurrentControlProfile := 2
        }
        else
        {
            YawInputAxis := InputStick YawInputAxisProfile1
            InvertY := PitchInvertedProfile1
            PitchInputAxis := InputStick PitchInputAxisProfile1                      
            RollInputAxis := InputStick RollInputAxisProfile1                      
            ThrottleInputAxis := InputStick ThrottleInputAxisProfile1
            CurrentControlProfile := 1
        }
        return
     
    WatchThrottle:
        ThrottleUpKey := ThrottleOutputKeys[2]
        ThrottleDownKey := ThrottleOutputKeys[1]
        if ((!WinActive(GameWindowName) && SupressJoystickOutputAwayFromGame = 1) || (!ThrottleControlEnabled))
        {
            if (ThrottleKeyState = 1)
            {
                Send, {%ThrottleUpKey% up}
                Send, {%ThrottleDownKey% up}
                ThrottleKeyState := 0
            }
            return
        }
        ThrottleAxis := GetKeyState(ThrottleInputAxis)  ; Get position of Throttle axis.
     
        if (ThrottleAxis > 50 + ThrottleDeadZone)
            KeyToHoldDownT := ThrottleOutputKeys[2]
        else if (ThrottleAxis < 50 - ThrottleDeadZone)
            KeyToHoldDownT := ThrottleOutputKeys[1]
        else
        {
            if (ThrottleKeyState = 1)
            {
                Send, {%ThrottleUpKey% up}
                Send, {%ThrottleDownKey% up}
                ThrottleKeyState := 0
            }
            return
        }
    
        SetKeyDelay -1  ; Avoid delays between keystrokes.
    
        if (ThrottleAxis > 90 || ThrottleAxis <10)  ; just holds key down when at max versus pulsing it
        {
            Send, {%KeyToHoldDownT% down}
            ThrottleKeyState := 1
            return
        }
    
        possibleT := 50 - ThrottleDeadZone
        rawAmountT := abs(ThrottleAxis - 50) ; Range is now -50 to +50
        correctedAmountT := rawAmountT - ThrottleDeadZone
     
        sendDurationRatioT := (minimumSendDurationRatioT + (correctedAmountT/possibleT)/(1-minimumSendDurationRatioT))
     
        Send, {%KeyToHoldDownT% down}  ; Press it down.
        Sleep, sendDurationRatioT * AxisSendModulationTimeT
        Send, {%KeyToHoldDownT% up}  ; Release it.
        Sleep, (1- sendDurationRatioT) * AxisSendModulationTimeT
    
        return
    
    ; Remap buttons, and make up event of buttons fire when button is actually released
    ButtonPressed(btn){
        global ButtonKeys, InputStick, Buttonstrings, ButtonKeyStrings
        Send % ButtonKeyStrings[btn].down
        Send % "{" ButtonKeys[A_Index] " down}"
        while(GetKeyState(Buttonstrings[btn])){
            Sleep 10
        }
        Send % ButtonKeyStrings[btn].up
    }
    
    
    
    
    
     
    #1
    Last edited: Jul 16, 2020
  2. Kassonnade

    Kassonnade Rear Admiral

    Joined:
    May 13, 2017
    Messages:
    2,816
    Likes Received:
    4,111
    Nice work !
     
    #2
    Ephoie and Snowmobeetle like this.
  3. IndigoWyrd

    IndigoWyrd Rear Admiral

    Joined:
    Jun 19, 2018
    Messages:
    1,028
    Likes Received:
    1,414
    If only AutoHotKey didn't require two college courses to make work....
     
    #3
    Ephoie, Snowmobeetle and Bollen like this.
  4. Bollen

    Bollen Captain

    Joined:
    Mar 22, 2017
    Messages:
    253
    Likes Received:
    250
    This is amazing and it boggles the mind that devs haven't done this themselves. I am sure I am not alone in thanking you for this extraordinary work!

    As a side note/request, could you perhaps do a step by step installation guide for those of us who are, unfortunately, not very technical-minded?
     
    #4
    Snowmobeetle likes this.
  5. Snowmobeetle

    Snowmobeetle Ensign

    Joined:
    Jul 14, 2020
    Messages:
    11
    Likes Received:
    20
    I agree AHK is crazy to work with, but it's not hard to grab an existing script and run it. this should be pretty much plug and play for Empyrion.

    To use the script:

    - install AutoHotKey
    - copy the code I wrote into a text editor (notepad is fine), and save as Something.AHK
    -----edit: (or download the AHK file I linked in the orignal post)
    - double click on the file
     
    #5
    Last edited: Jul 16, 2020
    Ephoie and Bollen like this.
  6. Bollen

    Bollen Captain

    Joined:
    Mar 22, 2017
    Messages:
    253
    Likes Received:
    250
    Sweet! So basically do not use JoyToKey with it...? And presumably also the default/factory keybinds in Empyrion? If both the latter, then I suppose one needs to configure keybinds in the script itself?

    And finally. in JoyToKey, I have a profile switch that allows me to change the x,y,z according to if I'm using a ship or an HV, can your script do this?
     
    #6
    Ephoie likes this.
  7. Snowmobeetle

    Snowmobeetle Ensign

    Joined:
    Jul 14, 2020
    Messages:
    11
    Likes Received:
    20
    Correct, it doesn't require any other software in tandem.

    EDIT: I added a feature to switch between two profiles for axis assignment and pitch inversion

    There are variables for binds in a section marked at the top of the script. It's mostly self-explanatory (read the comments to the right of each line)

    Note: AHK swaps JoyZ and JoyR, so use JoyZ for throttle input and JoyR for Z axis input, if you change the defaults

    EDIT: I removed old info, keybinds are mostly self-explanatory and better explained in the script now.

    The default controls in the script are:

    Profile 1:
    - Joystick X: roll
    - Joystick Y: inverted pitch
    - Joystick Z: yaw (what the mouse does by moving right, turn the ship right like a car)

    Profile 2:
    - Joystick X: roll
    - Joystick Y: pitch
    - Joystick Z: yaw

    - Profile switch button: -


    Universal bindings (apply for both profiles):
    - Hat left: a (strafe left)
    - Hat right: d (strafe right)
    - Hat Up: space (lift)
    - Hat Down: c (lower)
    - Joy1 (trigger): Left Mouse Click
    - Joy2 (thumb button): the letter o (levels ship)
    - Joy3 through Joy8 correspond to 1 - 6 keys, which select weapons/sensors
    - Joy Throttle: w and s keys
    - Joy9 is bound to the in-script feature that toggles the throttle system on and off. You'll need it, because the throttle can't work if you go into a menu or want to switch to windows. You can change the binding to a different joystick button or a keyboard key (or mouse button) if you want.
     
    #7
    Last edited: Jul 16, 2020
    Ephoie and Bollen like this.
  8. Dreuseff

    Dreuseff Lieutenant

    Joined:
    Jan 13, 2017
    Messages:
    24
    Likes Received:
    31
    I cannot describe to you how happy this makes me.

    As an aside: I don't suppose you've heard of Voice Attack? Unloading menu navigation from HOTAS to voice commands is amazeballs. If you haven't, check out the HCS Voicepacks for Elite Dangerous for inspiration.
     
    #8
  9. Snowmobeetle

    Snowmobeetle Ensign

    Joined:
    Jul 14, 2020
    Messages:
    11
    Likes Received:
    20
    Thanks!

    Yes, I did mess around with Voice Attack and Elite Dangerous a while back. I had three 24" gaming monitors set up, and a Tobii 4c tracking my eye and head movements. Awesome flight experience and feel, but it got boring after a while since there wasn't much to do (I know they added more stuff, I might check it out again some day).


    Bollen - I went ahead and added a feature to the script (already changed in the original post) to toggle between two axis setting profiles with the "-" key (can be changed to another key or joystick button if desired). Wasn't too hard! It also made me find another minor code error that made the whole script run even smoother for joystick movement.
     
    #9
    Bollen likes this.
  10. Snowmobeetle

    Snowmobeetle Ensign

    Joined:
    Jul 14, 2020
    Messages:
    11
    Likes Received:
    20
    EDIT: I Added a feature to switch between Profile1 and Profile2 with the "-" key (changeable). Currently this just supports changing the XYZ axes assignment and Y inversion settings on the fly. I also corrected a small bug in the xy code (it was reading below deadzone unintentionally before sometimes)

    EDIT 2: I cleaned up the binding section a LOT so it's WAY easier to understand
     
    #10
    Bollen likes this.
  11. That Blue Stuff

    That Blue Stuff Ensign

    Joined:
    Jul 16, 2020
    Messages:
    1
    Likes Received:
    1
    Can you add a feature to auto chat random Macho Man Randy Savage quotes?
     
    #11
    Snowmobeetle likes this.
  12. Snowmobeetle

    Snowmobeetle Ensign

    Joined:
    Jul 14, 2020
    Messages:
    11
    Likes Received:
    20
    TOTALLY

    That actually wouldn't be hard.

    the period key "." opens chat, enter key sends, escape key closes chat. You'd see it flash up probably, but it would work.

    Every time you fire a weapon, you get a Savage quote. I like it.

    I'm not actually gonna do it, but I could.
     
    #12
    Ephoie and Bollen like this.
  13. Bollen

    Bollen Captain

    Joined:
    Mar 22, 2017
    Messages:
    253
    Likes Received:
    250
    So I suppose the question now is, how do I use it with multiple joysticks? i.e. separate throttle and pedals...>
     
    #13
  14. Snowmobeetle

    Snowmobeetle Ensign

    Joined:
    Jul 14, 2020
    Messages:
    11
    Likes Received:
    20
    It actually can support that if the main joystick is 1 and the foot pedal is 2

    make
    InputStick := ""

    Then for the axis assignments, put int "1JoyX", "1JoyY", etc for first joystick and "2JoyX" "2JoyY", etc for the foot pedal. I'm guessing the footpedal input will be detected as "2JoyZ". It will take some trial and error to see which is joystick 1 and 2.

    I'm busy atm, but I'll make the script support dual joysticks properly - won't be hard.
     
    #14
    Bollen likes this.
  15. Bollen

    Bollen Captain

    Joined:
    Mar 22, 2017
    Messages:
    253
    Likes Received:
    250
    Sweet! Would that apply to triple too? i.e. Stick, throttle & pedals?
     
    #15
  16. Nauttdog (naut-T-dog)

    Nauttdog (naut-T-dog) Lieutenant

    Joined:
    Dec 2, 2016
    Messages:
    20
    Likes Received:
    15
    Will this work with my Thrustmaster T.16000M FCS HOTAS?
     
    #16
  17. Bollen

    Bollen Captain

    Joined:
    Mar 22, 2017
    Messages:
    253
    Likes Received:
    250
    Yes! I have exactly the same and it worked pretty sweet, however... I have not been able to get the others (throttle and pedals) to work...:(
     
    #17
  18. Bollen

    Bollen Captain

    Joined:
    Mar 22, 2017
    Messages:
    253
    Likes Received:
    250
    OK, so here it is working for 3 inputs (assuming joystick 1, throttle 2 and pedals 3). I've tweaked it a bit so that turning in HV also has a bit of roll and I've disabled the throttle because I can't get it to work smoothly. However, that can be enabled in line 63 (simply change 0 for 1). I also strongly recommend using JoyToKey or similar for the rest of the buttons.

    Code:
    #SingleInstance force
    #Persistent
    
    ;  FLIGHT STICK/HOTAS MAPPING SCRIPT FOR EMPYRION - GALACTIC SURVIVAL (v12 tested)
    
    ; This is an AutoHotKey script, and requires that (free) application to run
    ; Save this code in a text file and give it the extension *.AHK, then double-click it after instaling AutoHotKey
    
    applicationname=EmpyrionGalacticFlightStickScript
    
    ; === Edit these vars to set up the script to your liking ===
    
    SupressJoystickOutputAwayFromGame := 0                        ; 1 for supress, 0 for don't supress (useful for testing)
    GameWindowName := "Empyrion - Galactic Survival"            ; Supresses joystick output unless this is the active window
    
    InputStick := ""                                                ; The ID of your input stick
    
    ; Assignments of Axes from joystick inputs
    ;   Options are:
    ;        Joystick X Axis:             JoyX
    ;        Joystick Y Axis:             JoyY
    ;        Joystick Z Axis:             JoyR   ;R and Z are backwards because AHK has them reversed
    ;        Joystick Throttle Axis:     JoyZ
    
    ; Profile 1
    YawInputAxisProfile1 := "3JoyR"                                   
    PitchInputAxisProfile1 := "1JoyY"                               
    RollInputAxisProfile1 := "1JoyX"                                       
    ThrottleInputAxisProfile1 := "2JoyZ"                                   
    PitchInvertedProfile1 := 1
    
    
    ; Profile 2  currently set up to switch roll and yaw (leaning stick side to side turns instead of rolls) and does not invert the joystick
    YawInputAxisProfile2 := "1JoyX"                               
    PitchInputAxisProfile2 := "1JoyY"                           
    RollInputAxisProfile2 := "1JoyX"                               
    ThrottleInputAxisProfile2 := "2JoyZ"
    PitchInvertedProfile2 := 1
    
    ; Button That Switches Between Profile 1 and Profile 2 Axis Assignments
    ProfileToggleKey := "-"                                       
    
    
    ; Dead Zone adjustments (prevents drift when an analog axis is centered but still sending a tiny signal)
    PitchDeadZone := 15                                            ; Percentage of deadzones per axis
    RollDeadZone := 15
    YawDeadZone := 20
    ThrottleDeadZone := 30
    
    ; Sensitivity Adjustments.  Use 0.1 to 2 or so. will depend on your preferred mouse sensitivity in-game.
    RollSensitivity := 0.2                                       
    PitchSensitivity := 0.2
    YawSensitivity := 0.5                                       
    
    RollAxisOutputKeys := ["q", "e"]                            ; Array of keys to map to the Roll Axis
    ThrottleOutputKeys := ["Ctrl"]                                ; Array of keys to map to the Throttle axis
    
    ; Assign a button that turns the thrust control on and off in-game
    ThrottleControlDisableEnableKey := "1Joy9"                    ; Assign a button or key that will enable/disable the throttle control in-game (makes the analog control more friendly - you don't have to center it to use menus or switch windows)
                                                                ; If you Assign a Joystick Button Here, put the number of the joystick, then "Joy", then the button number: 1Joy9 for Joystick 1 button 9 (joystick 1 is default)
                                                                
    ; Set this to 0 to disable throttle function entirely                                                           
    EnableThrottleBinding := 0                                    ; Throttle works well, but can't work when in menus and will prevent Alt+Tab to switch apps in windows... (due to empyrion limitations)
    
    HatKeysX := ["a","d"]                                        ; Array of keys to map to the POV hat X axis
    HatKeysY := ["space","c"]                                    ; Array of keys to map to the POV hat Y axis
    
                            ; NOTES ON HOW TO CHANGE KEYBINDINGS:
    Joy1 := "LButton"         ;     LButton, MButton, RButton are the middle, left, and right mouse buttons
    Joy2 := "o"                ;     enter lowercase letters or numbers in the quotes for binds
    Joy3 := "1"                ;     Space, Tab, Enter, Escape, LShift, RShift, LAlt, RAlt, LControl, RControl, F1, F2, ...,  are other common keynames for keys
    Joy4 := "2"                ;     use a carat before a key to bind a control+keypress:       ^p     would bind Ctrl+p
    Joy5 := "3"                ;   use a plus before a key to bind a Shift+keypress:       +p     would bind Shift+p
    Joy6 := "4"                ;   use an exclamation mark before a key for Alt+keypress:    !p    would bind Alt+p
    Joy7 := "5"               
    Joy8 := "6"                ; For more info on what to put in for keys, visit: https://www.autohotkey.com/docs/KeyList.htm
    Joy9 := ""
    Joy10 := ""                ; A button with nothing defined ("") will be skipped and not bound 
    Joy11 := ""                ;    - do this to choose not to bind the button or if you're using elsewhere (another script or for the ThrottleControlDisableEnableKey or ProfileToggleKey in this script)
    Joy12 := ""
    Joy13 := ""
    Joy14 := ""
    Joy15 := ""
    Joy16 := ""
    Joy17 := ""
    Joy18 := ""
    Joy19 := ""
    Joy20 := ""
    Joy21 := ""
    Joy22 := ""
    Joy23 := ""
    Joy24 := ""
    Joy25 := ""
    Joy26 := ""
    Joy27 := ""
    Joy28 := ""
    Joy29 := ""
    Joy30 := ""
    Joy31 := ""
    Joy32 := ""
    
    ; These adjust how long the pulse interval is for analog conversions of Yaw and Thrust.  Leave alone unless you're tinkering with the performance of this function
    AxisSendModulationTimeZ := 500                                  ; total time of keypress pulse loops for Roll (the key will be pressed down a fraction of this time depending on analog input
    AxisSendModulationTimeT := 50                              ; total time of keypress pulse loops for Thrust (the key will be pressed down a fraction of this time depending on analog input                       
    minimumSendDurationRatioT := 0.40                             ; Minimum ratio of full that Throttle starts on (lower values weren't working in empyrion, glitching)
                                                                
    ;====== End vars intended to be modified =======
    
    
    
    
    InvertY := PitchInvertedProfile1                            ; assigning profile 1 bindings do not change these                     
    YawInputAxis := InputStick YawInputAxisProfile1
    PitchInputAxis := InputStick PitchInputAxisProfile1                           
    RollInputAxis := InputStick RollInputAxisProfile1                           
    ThrottleInputAxis := InputStick ThrottleInputAxisProfile1
    
    Hotkey, %ThrottleControlDisableEnableKey%, ToggleThrottle    ; linking hotkey to function for this binding
    Hotkey, %ProfileToggleKey%, ToggleProfile                    ; linking hotkey to function for this binding
    ThrottleControlEnabled := 1       ; tracks whether user has disabled or enabled throttle control
    CurrentControlProfile := 1       ; tracks whether user is currently on control Profile 1 or 2 (don't change this)
    ThrottleKeyState := 0           ; internal program use
    RollKeyState := 0                ; internal program use
    PitchDeadZone := PitchDeadZone/2                            ; convert deadzone percentages to raw numbers (0-50 range)
    RollDeadZone := RollDeadZone/2
    YawDeadZone := YawDeadZone/2
    ThrottleDeadZone := ThrottleDeadZone/2
    
    if (HatKeysX.length() && HatKeysY.length()){
        HatKeys := []
        HatKeys[1] := [{down: "{" HatKeysX[1] " down}", up: "{" HatKeysX[1] " up}"},{down: "{" HatKeysX[2] " down}", up: "{" HatKeysX[2] " up}"}]
        HatKeys[2] := [{down: "{" HatKeysY[1] " down}", up: "{" HatKeysY[1] " up}"},{down: "{" HatKeysY[2] " down}", up: "{" HatKeysY[2] " up}"}]
        HatEnabled := 1
    } else {
        HatEnabled := 0
    }
    HatState := [0,0]                                            ; The current state of the hat X and Y axes
    ; Build an "Associative Array" map of hat angles to X and Y directions
    HatMap := {-1: [0,0], 0: [0,1], 4500: [2,1], 9000: [2,0], 13500: [2,2], 18000: [0,2], 22500: [1,2], 27000: [1,0], 31500: [1,1]}
    ; Pre-assemble GetKeyState strings for performance optimization
    JoyString := InputStick "Joy"
    AxisStrings := []
    HatString := JoyString "POV"
    Loop 2 {
        AxisStrings.push(JoyString Axes[A_Index])
    }
    
    
    
    
    ; Bind Normal Joystick Buttons 1-32
    
    
    ButtonStrings := []
    ButtonKeyStrings := []
    Loop 32
    {
        currJoyButton := % "Joy" A_Index
        currJoyButtonTarget := %currJoyButton%
        ButtonStrings[A_Index] := JoyString A_Index
        ButtonKeyStrings[A_Index] := {down: "{" currJoyButtonTarget " down}", up: "{" currJoyButtonTarget " up}"}
    }
    
    ;Bind Buttons
    Loop 32
    {
        currJoyButton := % "Joy" A_Index
        currJoyButtonTarget := %currJoyButton%
        if (currJoyButtonTarget != "")
        {
            fn := Func("ButtonPressed").Bind(A_Index)
            hotkey, % JoyString A_Index, % fn
        }
    }
    
    ; start functions that loop and handle axis inputs
    SetTimer, WatchHat, 5
    SetTimer, WatchTwoAxesForMouseOutput, 5
    SetTimer, WatchRoll, 5
    if (EnableThrottleBinding = 1)
        SetTimer, WatchThrottle, 5
        
    return
    
    WatchHat:
        if (!WinActive(GameWindowName) && SupressJoystickOutputAwayFromGame = 1)
            return
    
        ; === Hat ===
        if (!HatEnabled)
            return
        state := GetKeyState(HatString)
        ; Process Hat X axis then Y Axis
        Loop 2 {
            new_state := HatMap[state,A_Index]
            old_state := HatState[A_Index]
            if (old_state != new_state){
                if (old_state)
                    Send % HatKeys[A_Index, old_state].up
                if (new_state)
                    Send % HatKeys[A_Index, new_state].down
                HatState[A_Index] := new_state
            }
        }
        
        return
        
        
    WatchTwoAxesForMouseOutput:
        if (!WinActive(GameWindowName) && SupressJoystickOutputAwayFromGame = 1)
            return
    
        mouseAxisX := GetKeyState(YawInputAxis)-50  ; Get position of axis assigned for X, centered.
        MouseAxisY := GetKeyState(PitchInputAxis)-50  ; Get position of axis assigned for Y, centered.
    
        if (abs(mouseAxisX) > YawDeadZone || abs(MouseAxisY) > PitchDeadZone)
        {   
    
            if (InvertY = 1)
                MouseAxisY := -1 * MouseAxisY
    
            ;//factor in deadzone and scale back up
            if (mouseAxisX > YawDeadZone)
                mouseAxisX := (mouseAxisX - YawDeadZone) * 50 / (50-YawDeadZone)
            else if (mouseAxisX < 0 - YawDeadZone)
                mouseAxisX := (mouseAxisX + YawDeadZone) * 50 / (50-YawDeadZone)
            else
                mouseAxisX := 0
            
            if (MouseAxisY > PitchDeadZone)
                MouseAxisY := (MouseAxisY - PitchDeadZone) * 50 / (50-PitchDeadZone)
            else if (MouseAxisY < 0 - PitchDeadZone)
                MouseAxisY := (MouseAxisY + PitchDeadZone) * 50 / (50-PitchDeadZone)
            else
                MouseAxisY := 0
                
            DllCall("mouse_event", uint, 1, int, mouseAxisX * YawSensitivity, int, MouseAxisY * PitchSensitivity)
        }
    
        return
        
    WatchRoll:
        KeyToHoldDownRollR := RollAxisOutputKeys[2]
        KeyToHoldDownRollL := RollAxisOutputKeys[1]
        if (!WinActive(GameWindowName) && SupressJoystickOutputAwayFromGame = 1)
        {
            if (RollKeyState = 1)
            {
                Send, {%KeyToHoldDownRollR% up}
                Send, {%KeyToHoldDownRollL% up}   
                RollKeyState := 0
            }
            return
        }
            
        RollAxis := GetKeyState(RollInputAxis)  ; Get position of assigned roll axis.
        
        if (RollAxis > 50 + RollDeadZone)
            KeyToHoldDownRoll := RollAxisOutputKeys[2]           
        else if (RollAxis < 50 - RollDeadZone)
            KeyToHoldDownRoll := RollAxisOutputKeys[1]
        else
        {   
            if (RollKeyState = 1)
            {
                Send, {%KeyToHoldDownRollR% up}
                Send, {%KeyToHoldDownRollL% up}   
                RollKeyState := 0
            }
            return
        }
        
        SetKeyDelay -1  ; Avoid delays between keystrokes.
        
        if (RollAxis > 90 || RollAxis <10)  ; just holds key down when at max versus pulsing it
        {
            RollKeyState := 1
            Send, {%KeyToHoldDownRoll% down}   
            return
        }
    
        possibleZ := 50 - RollDeadZone
        rawAmountZ := abs(RollAxis - 50) ; Range is now -50 to +50
        correctedAmountZ := rawAmountZ - RollDeadZone
        
        Send, {%KeyToHoldDownRoll% down}  ; Press it down.
        Sleep, (correctedAmountZ/possibleZ) * AxisSendModulationTimeZ * RollSensitivity
        Send, {%KeyToHoldDownRoll% up}  ; Release it.
        Sleep, (1- correctedAmountZ/possibleZ) * AxisSendModulationTimeZ * RollSensitivity
    
        return
        
    ToggleThrottle:
        if (ThrottleControlEnabled = 1)
            ThrottleControlEnabled := 0
        else
            ThrottleControlEnabled := 1
        return
        
    ToggleProfile:
        if (CurrentControlProfile = 1)
        {
            YawInputAxis := InputStick YawInputAxisProfile2
            InvertY := PitchInvertedProfile2
            PitchInputAxis := InputStick PitchInputAxisProfile2                           
            RollInputAxis := InputStick RollInputAxisProfile2                           
            ThrottleInputAxis := InputStick ThrottleInputAxisProfile2
            CurrentControlProfile := 2
        }
        else
        {
            YawInputAxis := InputStick YawInputAxisProfile1
            InvertY := PitchInvertedProfile1
            PitchInputAxis := InputStick PitchInputAxisProfile1                           
            RollInputAxis := InputStick RollInputAxisProfile1                           
            ThrottleInputAxis := InputStick ThrottleInputAxisProfile1
            CurrentControlProfile := 1
        }
        return
        
    WatchThrottle:
        ThrottleUpKey := ThrottleOutputKeys[1]
        ThrottleDownKey := ThrottleOutputKeys[2]
        if ((!WinActive(GameWindowName) && SupressJoystickOutputAwayFromGame = 1) || (!ThrottleControlEnabled))
        {   
            if (ThrottleKeyState = 1)
            {
                Send, {%ThrottleUpKey% up}
                Send, {%ThrottleDownKey% up}   
                ThrottleKeyState := 0
            }
            return
        }
        ThrottleAxis := GetKeyState(ThrottleInputAxis)  ; Get position of Throttle axis.
        
        if (ThrottleAxis > 50 + ThrottleDeadZone)
            KeyToHoldDownT := ThrottleOutputKeys[2]
        else if (ThrottleAxis < 50 - ThrottleDeadZone)
            KeyToHoldDownT := ThrottleOutputKeys[1]
        else
        {
            if (ThrottleKeyState = 1)
            {
                Send, {%ThrottleUpKey% up}
                Send, {%ThrottleDownKey% up}   
                ThrottleKeyState := 0
            }
            return
        }
    
        SetKeyDelay -1  ; Avoid delays between keystrokes.
    
        if (ThrottleAxis > 90 || ThrottleAxis <50)  ; just holds key down when at max versus pulsing it
        {
            Send, {%KeyToHoldDownT% down}   
            ThrottleKeyState := 1
            return
        }
    
        possibleT := 50 - ThrottleDeadZone
        rawAmountT := abs(ThrottleAxis - 50) ; Range is now -50 to +50
        correctedAmountT := rawAmountT - ThrottleDeadZone
        
        sendDurationRatioT := (minimumSendDurationRatioT + (correctedAmountT/possibleT)/(1-minimumSendDurationRatioT))
        
        Send, {%KeyToHoldDownT% down}  ; Press it down.
        Sleep, sendDurationRatioT * AxisSendModulationTimeT
        Send, {%KeyToHoldDownT% up}  ; Release it.
        Sleep, (1- sendDurationRatioT) * AxisSendModulationTimeT
    
        return
    
    ; Remap buttons, and make up event of buttons fire when button is actually released
    ButtonPressed(btn){
        global ButtonKeys, InputStick, Buttonstrings, ButtonKeyStrings
        Send % ButtonKeyStrings[btn].down
        Send % "{" ButtonKeys[A_Index] " down}"
        while(GetKeyState(Buttonstrings[btn])){
            Sleep 10
        }
        Send % ButtonKeyStrings[btn].up
    }
    
    
    
    
     
    #18
    Kyodai and Snowmobeetle like this.
  19. Bollen

    Bollen Captain

    Joined:
    Mar 22, 2017
    Messages:
    253
    Likes Received:
    250
    @Snowmobeetle I wonder if there's a way to control the throttle using the CTRL key instead of just the W and S? If the same principle of accelarating repetition according to analogue input could be use but with the modifier always pressed, it should give a slower and more controllable throttle. I was able to sort of mimic this behaviour by having AHK control the W and S in your script and JoyToKey to trigger the CTRL and it gave me mixed results, most off them positive...
     
    #19
  20. Snowmobeetle

    Snowmobeetle Ensign

    Joined:
    Jul 14, 2020
    Messages:
    11
    Likes Received:
    20
    I've had trouble specifically with the ctrl key - seems like Empyrion likes to take over that key at a low system level that prevents AHK from using it well.

    I'm not particularly sure what you're suggesting. What is different about using Ctrl vs W and S? Are you suggesting that instead of using the key to toggle joystick throttle, there's a key that enables it only when held down?
     
    #20

Share This Page