. Hello. I made what could most likely be considered a.. tool? since it's not a mod, I guess. I'm not even sure whether this should go in this sub-forum or somewhere else, I apologize if I messed it up. Some days ago I was trying to find a good third party macro/rebinder so a friend of mine could play SB comfortably (who has only a laptop and nowhere comfortable to lay her mouse on). I tried mappers and other stuff, but no matter what was done the problems was always the same: not enough buttons, cursor too slow to react/turn around or too fast to use the inventory/crafting menu. Sure it's playable if you make the effort, but definitely not a comfortable experience. Then I stumbled upon Cragrim's (credits to him) thread, some old 2013 proof of concept AutoHotKey script. It gave me the key idea of force fixing the mouse to the center of the screen, and also an AutoHotKey script to tinker around with. So, one thing led to another, and a few days after I am finally left with nothing else to polish. So, I'm posting it in case any of you want to make use of it. Bear in mind this is a PS Controller design -Can either run on windowed maximized or full screen -Script makes use of a .ini file, In case you can't install AHK and feel like using the compiled version -You can remap and change the default controls in this .ini, plus extra adjustment settings Controls Some clarification on the above: -Free Aim: Just like a mouse cursor with some slight sensitivity acceleration, slow, meant for inventory management and maybe sniping creatures -Fixed Aim: Centers an imaginary bubble around the player, the position of the analog stick translates into which direction on the edge of the bubble (18 fixed directions) -Walk/Run mode: This is not a feature, just a hold shift till it toggles back kind of thing -Powers/Toolbar Toggle: Holding these down modifies the mapping of their respective side of the controller -Powers Toggle - Double Tap: Whenever the player is moving, if Powers Toggle is pressed/held down, the script will generate a quick interruption on the movement, performing a 'double tap', Intended for techs such as Dash. -Toolbar Toggle - Toolbar : Moving the "Aim" analog stick while holding Toolbar Toggle down will cause it to disregard any cursor positioning features and behave much like a mousewheel on the toolbar. -Slot 1 - 2 - 9 - 0 : Simple hotkeys for quick access to some slots on the toolbar Possible Issues: Fixed aim's 'bubble' might de-center in full screen mode, this is due to the fact the script works in full screen mode thanks to work around, since it can't retrieve or set any mouse position that isn't relative to the cursor. I've managed to reduce its occurrence to the point I haven't seen any de-centering in about an hour of testing. However, if it does happen to you, you can re-center the bubble by touching the camera button or by double tapping Toggle cursor mode It hasn't been tested on dual monitors, I have no clue what these affects regarding an AHK script, since this is the first time I use this program for something semi-elaborate. It probably can't be remapped into a xbox Controller. Or that's my guess. I don't really own an xbox controller And Lastly: the script is in no way elegant, this isn't an issue per se, but if you're a programmer you'll probably think I'm a moron for doing things this way, and for some part, you might be right. Other stuff, such as fixed statics positions around the bubble instead of trigonometricly calculated real time angles, have a very good reason to be that way. But disregard all of this. You can either download the compiled binaries (IE .exe), which would allow you to simply download and run it. BUT, you should never download an .exe from a stranger, and I've just come here. In case you trust me for whatever reason, I'll try to post a mutilated link so you take a wild guess where to download it because the board wont let me post any links or attach any files (fixed) https://mediafire.com/?vpdi64zeisg639l The files are clean, but just to be safe you could get a friend who has/knows AHK to compile it for you. and maybe even post it To run without AHK: .Download binaries .Make sure .exe is in same folder as SBC_config.ini .Plug in Controller .Run x 32 or x 64 .exe, depending on your OS To run on AHK: .You'll need AutoHotKey installed .Copy paste the ahk code below into wordpad/notepad/whatever .Save as .ahk .Copy paste the SBC_Config code under the ahk code into wordpad/notepad/whatever .Save as .ini .Put them in the same folder .Plug in Controller .Run AHK file Here's the ahk Code: Code: ;;;;;;;;;;;; ---- DECLARATIONS ---- ;;;;;;;;;;;; #Persistent #SingleInstance force #NoEnv SendMode Input SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory. ; Auto-detect the joystick number if called for: if JoystickNumber <= 0 { Loop 16 ; Query each joystick number to find out which ones exist. { GetKeyState, JoyName, %A_Index%JoyName if JoyName <> { JoystickNumber = %A_Index% break } } if JoystickNumber <= 0 { Msgbox No joypad was detected! Please connect a joypad and restart! ExitApp } } Msgbox, 0, SBC v1.1, SBC v1.1, SBC Running on Joypad %JoystickNumber% `nPlease use SBC_config.ini if you're experiencing any problems ;;;;;;;;;;;; ---- RETRIEVE CONFIG INFO ---- ;;;;;;;;;;;; IniRead, J_BT1, SBC_config.ini, Joy Bindings, Button1 IniRead, J_BT2, SBC_config.ini, Joy Bindings, Button2 IniRead, J_BT3, SBC_config.ini, Joy Bindings, Button3 IniRead, J_BT4, SBC_config.ini, Joy Bindings, Button4 IniRead, J_TGP, SBC_config.ini, Joy Bindings, TogglePowers IniRead, J_TGT, SBC_config.ini, Joy Bindings, ToggleToolbar IniRead, J_LC, SBC_config.ini, Joy Bindings, LeftClick IniRead, J_RC, SBC_config.ini, Joy Bindings, RightClick IniRead, J_IM, SBC_config.ini, Joy Bindings, InventoryMenu IniRead, J_CM, SBC_config.ini, Joy Bindings, CraftingMenu IniRead, J_TWLK, SBC_config.ini, Joy Bindings, ToggleWalk IniRead, J_TAIM, SBC_config.ini, Joy Bindings, ToggleAim IniRead, AnUKey, SBC_config.ini, Key Bindings, Analog1-Up IniRead, AnRKey, SBC_config.ini, Key Bindings, Analog1-Right IniRead, AnDKey, SBC_config.ini, Key Bindings, Analog1-Down IniRead, AnLKey, SBC_config.ini, Key Bindings, Analog1-Left IniRead, InvKey, SBC_config.ini, Key Bindings, InventoryMenu IniRead, CraKey, SBC_config.ini, Key Bindings, CraftingMenu IniRead, B1Key, SBC_config.ini, Key Bindings, Button1 IniRead, B2Key, SBC_config.ini, Key Bindings, Button2 IniRead, B3Key, SBC_config.ini, Key Bindings, Button3 IniRead, B4Key, SBC_config.ini, Key Bindings, Button4 IniRead, POVUKey, SBC_config.ini, Key Bindings, POV-Up IniRead, POVRKey, SBC_config.ini, Key Bindings, POV-Right IniRead, POVDKey, SBC_config.ini, Key Bindings, POV-Down IniRead, POVLKey, SBC_config.ini, Key Bindings, POV-Left IniRead, TB1Key, SBC_config.ini, Key Bindings, T_Button1 IniRead, TB2Key, SBC_config.ini, Key Bindings, T_Button2 IniRead, TB3Key, SBC_config.ini, Key Bindings, T_Button3 IniRead, TB4Key, SBC_config.ini, Key Bindings, T_Button4 IniRead, TAn2LKey, SBC_config.ini, Key Bindings, T_Analog2-Left IniRead, TAn2RKey, SBC_config.ini, Key Bindings, T_Analog2-Right IniRead, TPOVUKey, SBC_config.ini, Key Bindings, T_POV-Up IniRead, TPOVRKey, SBC_config.ini, Key Bindings, T_POV-Right IniRead, TPOVDKey, SBC_config.ini, Key Bindings, T_POV-Down IniRead, TPOVLKey, SBC_config.ini, Key Bindings, T_POV-Left IniRead, JThreshold, SBC_config.ini, Options, JoyThreshold IniRead, CSMultiplier, SBC_config.ini, Options, CursorSpeedMultiplier IniRead, Radius, SBC_config.ini, Options, FixedAimRadius IniRead, FAThreshold, SBC_config.ini, Options, FixedAimS IniRead, InvZR, SBC_config.ini, Options, InvertZ/R IniRead, FixY, SBC_config.ini, Options, FixCenterY IniRead, TimersBase, SBC_config.ini, Options, TimersFreqBase ;;;;;;;;;;;; ---- OPTIONS ---- ;;;;;;;;;;;; JoyMultiplier = %CSMultiplier% JoyThreshold = %JThreshold% ButtonTriangle := J_BT1 ButtonCircle := J_BT2 ButtonX := J_BT3 ButtonSquare := J_BT4 ButtonL2 := J_TGP ButtonR2 := J_TGT ButtonL1 := J_LC ButtonR1 := J_RC ButtonSelect := J_IM ButtonStart := J_CM ButtonL3 := J_TWLK ButtonR3 := J_TAIM ;;;;;;;;;;;; ---- ADJUSTMENTS ---- ;;;;;;;;;;;; JoyThresholdUpper := 50 + JoyThreshold JoyThresholdLower := 50 - JoyThreshold FAThresholdUpper := 50 + FAThreshold FAThresholdLower := 50 - FAThreshold KeyState := 0 Error := 0 Pause := false TogglePwr := false ToggleInv := false ToggleAim := false ToggleWalk := false Centered := false xx := 0 yy := 0 SCR_W := A_Screenwidth SCR_H := A_Screenheight if Radius = default Radiusv := (A_Screenheight/8) else Radiusv := Radius xc := Radiusv x1 := (Cos(0.314159265)*Radiusv) y1 := (Sin(0.314159265)*Radiusv) x2 := (Cos(0.62831853)*Radiusv) y2 := (Sin(0.62831853)*Radiusv) x3 := (Cos(0.942477795)*Radiusv) y3 := (Sin(0.942477795)*Radiusv) x4 := (Cos(1.25663706)*Radiusv) y4 := (Sin(1.25663706)*Radiusv) if InvZR = true { joyx2 = JoyR joyy2 = JoyZ } else { joyx2 = JoyZ joyy2 = JoyR } DPadT := TimersBase*4 WJoy1T := TimersBase*2 WJoy2T := TimersBase WChangesT := TimersBase*20 #SingleInstance SetTimer, DigitalPad, %DpadT% SetTimer, WatchJoystick, %WJoy1T% SetTimer, WatchJoystick2, %WJoy2T% SetTimer, WatchChanges, %WChangesT% ;;;;;;;;;;;; ---- HOTKEYS ---- ;;;;;;;;;;;; JoystickPrefix = %JoystickNumber%Joy Hotkey, %JoystickPrefix%%ButtonX%, ButtonX Hotkey, %JoystickPrefix%%ButtonL1%, ButtonL1 Hotkey, %JoystickPrefix%%ButtonR1%, ButtonR1 Hotkey, %JoystickPrefix%%ButtonSquare%, ButtonSquare Hotkey, %JoystickPrefix%%ButtonCircle%, ButtonCircle Hotkey, %JoystickPrefix%%ButtonTriangle%, ButtonTriangle Hotkey, %JoystickPrefix%%ButtonL2%, ButtonL2 Hotkey, %JoystickPrefix%%ButtonR2%, ButtonR2 Hotkey, %JoystickPrefix%%ButtonSelect%, ButtonSelect Hotkey, %JoystickPrefix%%ButtonStart%, ButtonStart Hotkey, %JoystickPrefix%%ButtonL3%, ButtonL3 Hotkey, %JoystickPrefix%%ButtonR3%, ButtonR3 return ;;;;;;;;;;;; ---- BUTTONS ---- ;;;;;;;;;;;; ButtonSquare: SetMouseDelay, -1 if ToggleInv Send, {%TB4Key%} else Send, {%B4Key%} return ButtonCircle: SetMouseDelay, -1 if ToggleInv Send, {%TB2Key%} else Send, {%B2Key%} return ButtonTriangle: SetMouseDelay, -1 if ToggleInv Send, {%TB1Key%} else Send, {%B1Key%} return ButtonSelect: SetMouseDelay, -1 Send, {%InvKey%} return ButtonStart: SetMouseDelay, -1 Send, {%CraKey%} return ButtonR3: SetMouseDelay, -1 if ToggleAim ToggleAim := false else { Centered := false ToggleAim := true } return ;;;;;;;;;;;; ---- BUTTONS HOLD ---- ;;;;;;;;;;;; ButtonX: SetMouseDelay, -1 if ToggleInv Send, {%TB3Key%} else { Send, {%B3Key% Down} SetTimer, WaitForXUp, 10 } return ButtonL1: SetMouseDelay, -1 MouseClick, left,,, 1, 0, D SetTimer, WaitForL1Up, 10 return ButtonR1: SetMouseDelay, -1 MouseClick, right,,, 1, 0, D SetTimer, WaitForR1Up, 10 return ButtonL2: SetMouseDelay, -1 TogglePwr := true SetTimer, WaitForL2Up, 10 return ButtonR2: SetMouseDelay, -1 ToggleInv := true SetTimer, WaitForR2Up, 10 return ButtonL3: if ToggleWalk { ToggleWalk := false Send {Shift up} } else { Send {Shift down} ToggleWalk := true } return WaitForXUp: if GetKeyState(JoystickPrefix . ButtonX) return SetMouseDelay, -1 SetTimer, WaitForXUp, off Send, {%B3Key% Up} return WaitForL1Up: if GetKeyState(JoystickPrefix . ButtonL1) return SetTimer, WaitForL1Up, off SetMouseDelay, -1 MouseClick, left,,, 1, 0, U return WaitForR1Up: if GetKeyState(JoystickPrefix . ButtonR1) return SetTimer, WaitForR1Up, off MouseClick, right,,, 1, 0, U return WaitForL2Up: if GetKeyState(JoystickPrefix . ButtonL2) return SetTimer, WaitForL2Up, off SetMouseDelay, -1 TogglePwr := false return WaitForR2Up: if GetKeyState(JoystickPrefix . ButtonR2) return SetTimer, WaitForR2Up, off ToggleInv := false return ;;;;;;;;;;;; ---- D PAD ---- ;;;;;;;;;;;; DigitalPad: SetMouseDelay, -1 ; Makes movement smoother. GetKeyState, POV, %JoystickNumber%JoyPOV KeyToHoldDownPrev = %KeyToHoldDown% ; Prev now holds the key that was down before (if any). if WinActive("ahk_class SDL_app") { ; Some joysticks might have a smooth/continous POV rather than one in fixed increments. ; To support them all, use a range: if POV < 0 ; No angle to report { KeyToHoldDown = } else if POV = 0 ; Up { If TogglePwr KeyToHoldDown = %TPOVUKey% else KeyToHoldDown = %POVUKey% } else if POV = 9000 ; Right { If TogglePwr KeyToHoldDown = %TPOVRKey% else KeyToHoldDown = %POVRKey% } else if POV = 18000 ; Down { If TogglePwr KeyToHoldDown = %TPOVDKey% else KeyToHoldDown = %POVDKey% } else if POV = 27000 ; Left { If TogglePwr KeyToHoldDown = %TPOVLKey% else KeyToHoldDown = %POVLKey% } if KeyToHoldDown = %KeyToHoldDownPrev% return ; Do nothing. if KeyToHoldDownPrev { Send, {%KeyToHoldDownPrev% up} } SetKeyDelay -1 ; Avoid delays between keystrokes. if KeyToHoldDown ; There is a key to press down. { Send, {%KeyToHoldDown% down} ; Press it down. } } return ;;;;;;;;;;;; ---- ANALOG 1 ---- ;;;;;;;;;;;; WatchJoystick: SetMouseDelay, -1 GetKeyState, joyx, %JoystickNumber%JoyX GetKeyState, joyy, %JoystickNumber%JoyY if KeyState = 0 { if joyy > %JoyThresholdUpper% { KeyState := 1 Send {%AnDKey% down} } else if joyx > %JoyThresholdUpper% { KeyState := 2 Send {%AnRKey% down} } else if joyx < %JoyThresholdLower% { KeyState := 3 Send {%AnLKey% down} } else if joyy < %JoyThresholdLower% { KeyState := 4 Send {%AnUKey% down} } } if KeyState = 1 { if joyy > %JoyThresholdUpper% { if TogglePwr and not Pause { Pause := true Send {%AnDKey% up} sleep 40 Send {%AnDKey% down} sleep 40 Send {%AnDKey% up} sleep 40 Send {%AnDKey% down} SetTimer PauseDelay, 300 } return } else Send {%AnDKey% up} KeyState := 0 } else if KeyState = 2 { if joyx > %JoyThresholdUpper% { if TogglePwr and not Pause { Pause := true Send {%AnRKey% up} sleep 40 Send {%AnRKey% down} sleep 40 Send {%AnRKey% up} sleep 40 Send {%AnRKey% down} SetTimer PauseDelay, 300 } return } else Send {%AnRKey% up} KeyState := 0 } else if KeyState = 3 { if joyx < %JoyThresholdLower% { if TogglePwr and not Pause { Pause := true Send {%AnLKey% up} sleep 40 Send {%AnLKey% down} sleep 40 Send {%AnLKey% up} sleep 40 Send {%AnLKey% down} SetTimer PauseDelay, 300 } return } else Send {%AnLKey% up} KeyState := 0 } else if KeyState = 4 { if joyy < %JoyThresholdLower% { if TogglePwr and not Pause { Pause := true Send {%AnUKey% up} sleep 40 Send {%AnUKey% down} sleep 40 Send {%AnUKey% up} sleep 40 Send {%AnUKey% down} SetTimer PauseDelay, 300 } return } else Send {%AnUKey% up} KeyState := 0 } return ;;;;;;;;;;;; ---- Avoid Pwr Spam ---- ;;;;;;;;;;;; PauseDelay: Pause := false SetTimer, PauseDelay, off return ;;;;;;;;;;;; ---- ANALOG 2 ---- ;;;;;;;;;;;; WatchJoystick2: SetFormat, float, 03 GetKeyState, joyr, %JoystickNumber%%joyx2% GetKeyState, joyz, %JoystickNumber%%joyy2% MouseNeedsToBeMoved := false OldPosition := Position oldxx := xx oldyy := yy if ToggleInv ;;;;;;;;;;;; ---- Toolbar Scrolling ---- ;;;;;;;;;;;; { if joyr > %JoyThresholdUpper% { Send {%TAn2RKey%} sleep 200 } if joyr < %JoyThresholdLower% { Send {%TAn2LKey%} sleep 200 } return } else if ToggleAim ;;;; ;;;;;;;;;;;; ---- Fixed Aim ---- ;;;;;;;;;;;; ;;;; { if (joyr > JoyThresholdUpper or joyr < JoyThresholdLower or joyz > JoyThresholdUpper or joyz < JoyThresholdLower) ; If Movement is detected { if (joyr > JoyThresholdUpper) Right := true else if (joyr < JoyThresholdLower) Right := false If Centered { if Right ;;;;; - Right - ;;;;; { if (joyr > JoyThresholdUpper and joyz < JoyThresholdLower) { if (joyz > FAThresholdLower) { xx := x1 yy := -y1 } else if (joyr < FAThresholdUpper) { xx := x3 yy := -y3 } else { xx := x2 yy := -y2 } } else if (joyr > JoyThresholdUpper and joyz > JoyThresholdUpper) { if (joyz < FAThresholdUpper) { xx := x1 yy := y1 } else if (joyr < FAThresholdUpper) { xx := x3 yy := y3 } else { xx := x2 yy := y2 } } else if (joyz < JoyThresholdLower) { xx := x4 yy := -y4 } else if (joyz > JoyThresholdUpper) { xx := x4 yy := y4 } else { xx := xc yy := 0 } } else ;;;;; - Left - ;;;;; { if (joyr < JoyThresholdLower and joyz < JoyThresholdLower) { if (joyz > FAThresholdLower) { xx := -x1 yy := -y1 } else if (joyr > FAThresholdLower) { xx := -x3 yy := -y3 } else { xx := -x2 yy := -y2 } } else if (joyr < JoyThresholdLower and joyz > JoyThresholdUpper) { if (joyz < FAThresholdUpper) { xx := -x1 yy := y1 } else if (joyr > FAThresholdLower) { xx := -x3 yy := y3 } else { xx := -x2 yy := y2 } } else if (joyz < JoyThresholdLower) { xx := -x4 yy := -y4 } else if (joyz > JoyThresholdUpper) { xx := -x4 yy := y4 } else { xx := -xc yy := 0 } } if (xx != oldxx) or (yy != oldyy) ;;;;; - Cursor Re-Positioning - ;;;;; { SetMouseDelay, -1 if Fullscreen MouseMove, xx - oldxx, yy - oldyy, 0, R else DllCall("SetCursorPos", int, (A_ScreenWidth/2+xx), int, (A_ScreenHeight/2+yy-FixY)) return } } else ;;;;; - Cursor Re-Centering - ;;;;; if Fullscreen { SetMouseDelay, -1 Loop, 4 { MouseMove, -3000, -3000, , R sleep 20 } MouseMove, 3000, 3000, , R sleep 20 Centered := true xx := 0 yy := 0 } else Centered := true } return } else ;;;; ;;;;;;;;;;;; ---- Free Aim ---- ;;;;;;;;;;;; ;;;; { if joyr > %JoyThresholdUpper% { MouseNeedsToBeMoved := true DeltaX := joyr - JoyThresholdUpper } else if joyr < %JoyThresholdLower% { MouseNeedsToBeMoved := true DeltaX := joyr - JoyThresholdLower } else DeltaX = 0 if joyz > %JoyThresholdUpper% { MouseNeedsToBeMoved := true DeltaY := joyz - JoyThresholdUpper } else if joyz < %JoyThresholdLower% { MouseNeedsToBeMoved := true DeltaY := joyz - JoyThresholdLower } else DeltaY = 0 if MouseNeedsToBeMoved { SetMouseDelay, -1 ; Makes movement smoother. MouseMove, DeltaX * JoyMultiplier, DeltaY * JoyMultiplier, 0, R Centered := false } return } return ;;;;; - Watch for Res/Window Changes - ;;;;; WatchChanges: MouseGetPos, x0, y0 If Fullscreen { if (x0 != (A_Screenwidth/2)) and (y0 != (A_Screenheight/2)) { if Error >= 6 { Fullscreen := false Error := 0 } else Error ++ } else Error := 0 } else { if (x0 = (A_Screenwidth/2)) and (y0 = (A_Screenheight/2)) { if Error >= 6 { Fullscreen := true Error := 0 Centered := false } else Error ++ } else Error := 0 } if (SCR_W != A_Screenwidth) or (SCR_H != A_Screenheight) { SCR_W := A_Screenwidth SCR_H := A_Screenheight if Radius = default Radiusv := (A_Screenheight/8) else Radiusv := Radius xc := Radiusv x1 := (Cos(0.314159265)*Radiusv) y1 := (Sin(0.314159265)*Radiusv) x2 := (Cos(0.62831853)*Radiusv) y2 := (Sin(0.62831853)*Radiusv) x3 := (Cos(0.942477795)*Radiusv) y3 := (Sin(0.942477795)*Radiusv) x4 := (Cos(1.25663706)*Radiusv) y4 := (Sin(1.25663706)*Radiusv) Centered := false } return And Here's the SBC_config.ini file: Code: [Joy Bindings] Button1=1 Button2=2 Button3=3 Button4=4 TogglePowers=5 ToggleToolbar=6 LeftClick=7 RightClick=8 InventoryMenu=9 CraftingMenu=10 ToggleWalk=11 ToggleAim=12 [Key Bindings] Analog1-Up=W Analog1-Right=D Analog1-Down=S Analog1-Left=A InventoryMenu=I CraftingMenu=C Button1=9 Button2=0 Button3=Space Button4=E POV-Up=2 POV-Right=Control POV-Down=Shift POV-Left=1 //Toggle Toolbar// T_Button1=Q T_Button2=Y T_Button3=X T_Button4=R T_Analog2-Left=WheelUp T_Analog2-Right=WheelDown //Toggle Powers// T_POV-Up=Escape T_POV-Right=H T_POV-Down=G T_POV-Left=F [Options] //Increase this if your controller's analog stick is slightly off-center JoyThreshold=1 //Modifying this value will speed up or slow down the cursor in free aim mode CursorSpeedMultiplier=0.08 //Modify this value to change the fixed aim sphere's radius (default uses screen resolution to calculate an appropriate value) FixedAimRadius=default //1-49, Lower this value if your analog stick is sensitive enough FixedAimS=45 //Change to true/false if your analog stick's axis seem to be inverted InvertZ/R=true //Only change this if you're playing on windowed mode and can't seem to make the window fit the whole screen, this moves the center up or down (default = 0) FixCenterY=10 //Probably shouldn't touch this TimersFreqBase=10