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 }
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?
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
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?
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.
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.
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.
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
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.
So I suppose the question now is, how do I use it with multiple joysticks? i.e. separate throttle and pedals...>
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.
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...
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 }
@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...
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?