[Tool] AutoHotkey scripts

Absolutely great! What an idea!

Hey, I´m currently struggling with something:
As long as my LMB is pressed, I want to apply ABB every 4 seconds and 2 other buffs every 30 seconds.
I cannot make two separate loops for that, as far as I can see - since the 1st loop never ends. Any quick suggestions? Would go for a counter being checked by modulo 30, but idk how to write this in “script”.

Better autocasting of multiple skills (frequencies, delays)

I’ve written such stuff for Diablo 3 but the code was very complicated (the loop was external always working in the background, different skills had different frequencies, you could pause them, toggle some of the skills etc) and I used the modulo (it’s clean and great for minimizing number of threads). It doesn’t need to be complicated though

Let’s assume the step for Modulo is 100 ms (instead of 1000 ms) so that we can set the frequencies to 1300 ms, 2700 ms etc instead of just 1, 2, 3, 4 s (can be changed to bigger 200, 500, 1000 ms if there are some performance issues)

Now we want choose the big number we are going to divide to check whether it’s time to use our skills. You want your skills to be cast 1) every 40 x 100 ms 2) every 300 x 100 ms. We want to choose a number that is divisible buy both 40 and 300. Let’s take the Lowest Common Multiple

LCM(40, 300) = 600
If we choose for example 3 skills with 1.3 s, 2.5 s and 10 s (13 x 100ms , 25 x 100 ms, 100 x 100 ms)
we would take
LCM(13, 25, 100) = 1300

Testing code for you to test

; my settings, highly recommend them for your scripts, they make a difference
SendMode Input
#NoEnv
#SingleInstance Force
#MaxHotkeysPerInterval 1000 
#MaxThreadsPerHotkey 1
#KeyHistory 50
SetWorkingDir %A_ScriptDir%

~*LButton::       ; * makes it work with Shift and other modifiers
    KeyWait, LButton, T0.2
    if ErrorLevel
    {
        counter := 0 ;0 for the first skill to be casted ASAP
                     ;1 for the first skill to be casted later
                     
        while GetKeyState("LButton", "P")
        {
            if !Mod(counter, 30) ;every 30 x 100 ms
                send {1}
            
            if !Mod(counter - 5, 30) ;every 30 x 100 ms, -5 for the 2th skill to be cast 0.5s later
                send {2}         ; to avoid skill conflict when you just started holding The Left Click
                
            if GetKeyState("Shift", "P") and !Mod(counter - 10, 30) ;combining with Shift for fun
                send {3}              ;every 30 x 100 ms, -10 for the 3th skill to be cast 1s later 
                                     ; to avoid conflict when you just started holding Left Click
                
            Sleep, 100 ; 100 ms instead of 1s for more freedom
            counter := Mod(counter + 1, 6000) ; in this example it could be 30 because
                                       ;  LCM(30, 30, 30) = 30 or any multiple of 30 like 6000
        }
    }
Return

The code doing exactly what you wanted (worse for testing, only changed fragment)

if !Mod(counter, 40) ;every 40 x 100 ms
                    send {1}
                
                if !Mod(counter - 5, 300) ;every 300 x 100 ms, -5 for the 2th skill to be cast 0.5s later
                    send {2}         ; to avoid skill conflict when you just started holding The Left Click
                    
                if !Mod(counter - 10, 300) 
                    send {3}              ;every 300 x 100 ms, -10 for the 3th skill to be cast 1s later 
                                         ; to avoid conflict when you just started holding Left Click
                    
                Sleep, 100 ; 100 ms instead of 1s for more freedom
                counter := Mod(counter + 1, 600) ; LCM(40, 300, 300) = 600
                                                          ; or any multiple of 600 i.e 40 x 300 x 300

The problem is in Grim Dawn when you want to cast 2-3 skills at the same time sometimes not all of them fire (the skills interrupt each other). You know, when you have your skill to be casted every 30s and it for some reason it’s not casted and you have to wait another 30s? You have a problem. My workaround for that has been trying to cast skills more often than needed (for example I would try to cast Totem/Wind Devil every 1s not every 5s) but the workaround has to be always tailored to the specific case. In this code I added time shifts for 2th, 3rd skills (counter -5, counter - 10) to prevent this when you start holding the Left Click. This could be combined with higher frequencies to be sure, for example trying to cast skills every 15 seconds instead of 30 seconds. You could probably improve the code so that there’s never conflict between skills but the code complication would be far greater than the benefits. You could make the counter global, to change the behavior a little bit. You could make the buffs to fire only once etc.

Another little problem is these times probably won’t be precise. What I mean is for example when the loop Sleep is 100 ms, the loop step will probably take longer than 100 ms (? I don’t know for sure though)

There’s also a possibility to have external loop working constantly in the background and you only “toggling” it with a hotkey, not having to hold a button if needed.

Tweak the code to your liking, it has lots of freedom and many ways of adjusting autocasting so that it plays very well. Almost anything can be achieved in Autohotkey but sometimes it takes me weeks to figure out some bugs/behaviors. I have lots of fun writing and testing these scripts so write if you have any problems. And as it’s always the case I’ve learnt something and got the code I’ll use myself.

Video of Testing Code (at first we don’t hold shift so Devouring Swarm doesn’t fire)

PS Some new stuff

dual mode Left Click Hold
(cast some skills when you just hold Left Click and some more skills when you additionaly hold Shift)

~*LButton:: ; ~ means Left Click still clicks, * means it works with Shift, Ctrl etc
    KeyWait, LButton, T0.2
    if ErrorLevel
    {
        while GetKeyState("LButton", "P")
        {
            Send {7} ;key for Wind Devil
            
            if GetKeyState("Shift", "P") ;When Shift pressed do something else as well
                Send {8} ;key responsible for War Cry/Wendigo Totem which we only 
                
            Sleep, 500 ; sleep time low to cast skills with very little delay
        }
    }
Return

suspending only selected hotkeys with a hotkey (not all of them with Suspend function):

q::
    Hotkey, ~*LButton, toggle
    Hotkey, ~*RButton, toggle
Return

When I used to play a caster Ritualist before, the piano playstyle immediately made me dislike the build.

Then I figured out I can just use X-Mouse to make my left-click do its original purpose (use the skill assigned to it) while repeatedly executing simulated keystrokes as well (press keyboard keys where other skills are assigned) as long as the button is pressed down.

You can even add delays between keystrokes (to allow for some skill casting animations to complete before the next one) as well as delays on repeating the whole keystroke cycle while the mouse button is held down (to prevent X-Mouse from immediately repeating the same keystrokes especially for skills with cooldowns).

XMouseButtonControl_2019-12-31_00-59-14

For keyboard stuff, AutoHotkey is your go-to.

1 Like

Thanks a lot again for the info!

My current code is different with 3 skills each with different behaviour

  • Wind Devil - cast as soon as cooldown ends when you hold Left Click (key sent every 200 ms)

  • War Cry - cast as soon as cooldown ends when you hold both Left Click and Shift (key sent every 200ms)

  • Wendigo Totem - cast only once as soon as cooldown ends when you hold both Left Click and Shift and then it waits for 14 seconds to cast it again unless you release, click and hold Shift again, then you can cast it sooner (as soon as cooldown ends if you click and release Shift frequently, i.e. if moving from pack to pack when you want Totems more often) [this is how I play - I hold Left Click permanently, hold Shift when fighting one pack, Right click for movement skill]

There’s a mechanism to ensure than Wendigo Totem is definitely cast and not interrupted by Primal Strike from Left Click or other skills

~*LButton::
    KeyWait, LButton, T0.2
    if ErrorLevel
    {    
        counter := 0
        while GetKeyState("LButton", "P")
        {
            if GetKeyState("Shift", "P")
            {
                if counter < 10 ;we try casting Totem 10 times (for 10 x 200 ms = 2s) for 100% chance
                {
                    Send {7} ; Wendigo Totem
                }
                Send {6} ; War Cry
                counter := Mod(counter + 1, 70) ;70 means 70 x 200 ms = 14s because Totem has 15s duration
            }
            else
                counter := 0
            Send {8} ; Wind Devil        
            Sleep, 200
        }
    }
Return

Hide All Items (Loot Filter) during fight

  1. Short version if you fight with Left Click and have a simple script

    ~*LButton::
     Send {LAlt}
    Return
    
    ~*LButton UP::
     Send {LAlt}
    Return
    
  2. Longer version example (when you move with Left Click & fight with Left Click + Shift)

    ~*LButton::
     KeyWait, LButton, T0.2
     if ErrorLevel
     {    
         items_hidden := false ;ITEMS SHOWN BEFORE THE FIGHT
       
       while GetKeyState("LButton", "P")
     {   
         ;SKILLS CAST WHILE WALKING
         
         if GetKeyState("Shift", "P") ;FIGHT MODE
         {
             if !items_hidden ;HIDING ITEMS IF SHIFT IS HELD
             {
                 Send {LAlt}
                 items_hidden := true
             }
             
             ;SKILLS CAST WHILE FIGHTING
         }
         else
         {
             if items_hidden ; SHOWING ITEMS IF SHIFT IS RELEASED FIRST
             {
                 items_hidden := false
                 Send {LAlt}
             }
         }
         Sleep, 200
     }
     }
    Return
    
     ~*LButton UP:: ;SHOWING ITEMS IF LEFT CLICK IS RELEASED FIRST
     if items_hidden
     {
         items_hidden := false
         Send {LAlt}
     }
    Return

Again, what a nice idea!

Thanks, ideas keep coming naturally when you are too lazy to practice execution/click many buttons/master piano builds :grin: and I really don’t like focusing on cooldowns while fighting

; first I've done it with external main loop and Sleep but
; setTimer function is actually simpler and more powerful

SendMode Input
#NoEnv
#SingleInstance Force
#MaxHotkeysPerInterval 1000
#MaxThreadsPerHotkey 1
#KeyHistory 50
SetWorkingDir %A_ScriptDir%

doom_bolt_allowed := true

AllowDoomBolt()
{
    global doom_bolt_allowed
    doom_bolt_allowed := true
}

~*RButton::
    KeyWait, RButton, T0.2
    if ErrorLevel
    {
        counter := 0

        while GetKeyState("RButton", "P")
        {
            if doom_bolt_allowed
            {
                send {0}
                doom_bolt_allowed := false
                SetTimer, AllowDoomBolt, -13000
                /*
                    '-13000' above makes AllowDoomBolt run only once after 13s
                    whereas with '13000' the function would run every 13s
                */                
            }
            
            if !Mod(counter-5, 40) ;blood of dreeg
                send {9}
                
            if !Mod(counter-20, 100) ;seal of consumption
                send {8}
                
            Sleep, 100
            counter := Mod(counter + 1, 2600)                
        }
    }
Return

Code can be probably rewritten with Timers only instead of the counter.

OUTDATED there’s a better version without loop and sleeps using timers
Automatic camera (following your character)


b - rotate left
n - rotate right
~*LButton::
    KeyWait, LButton, T0.2 ;need to hold the button for 200ms to follow
    if ErrorLevel
    {         
        while GetKeyState("LButton", "P")
        {
            WinGetActiveStats, Title, Width, Height, X, Y
            MouseGetPos, xpos, ypos 
            xpos := xpos - Width/2 ;vector from the middle of the screen to the cursor
            ypos := Height/2 - ypos
            
            if xpos*xpos + ypos*ypos < 40000 ;<200 pix from center -> no rotation
                continue

            if (ypos > 0) and ( Abs(ATan(xpos / ypos)) * 57.29578 < 20) 
                continue ; 20° degrees from 12 o'clock -> 40° no rotation sector
            
            if xpos > 0 ;right half of screen -> rotate right
            {
                Send {n down}
                Sleep, 100 ; haven't tested other values here
                Send {n up}
            }
            else ;left half of screen -> rotate left
            {
                Send {b down}
                Sleep, 100
                Send {b up}
            }
            
            Sleep, 20
        }
    }
Return

[edit] added 200 pixel protection (no rotation if cursor is closer than 200 pixels to the middle of the screen) so that there’s no rotation during melee fights

[edit2] changing sector of no rotation near 12 o’clock from 10 x 2 = 20° to 20 x 2 = 40° degrees so that there’s less rotation and direction changes when moving upwards and changing direction only slightly; set this parameter to your liking

1 Like

this is a-freakin-mazing!

1 Like

If I just copy your text now AHK open an error message which says it doesn’t accept ;

Fixed. The ‘;’ shouldn’t be right after the code, there should be ’ ’ (space) in between

BTW another thing that could be added to the script is blocking the rotation for some time after you press buttons responsible for using skills

For example when you use a Movement Skill, the rotation stops (because it’s a little bit weird when the screen is rotatin when you are in the middle of animation)

You can also check this script Faster camera rotation using keyboard or scroll wheel I’ve been using it and now I may switch to this (need to test it longer)

The biggest problem of the script is that if I minimize GD and want to use my mouse without typing b or n I have to close the script everytime :sweat_smile:

Can I configure a global pause key for AHK?

Here’s a fuller version of script with suspension of hotkeys when Grim Dawn is minimized (the initial setting are recommended as well)

SendMode Input
#NoEnv
#SingleInstance Force
#MaxHotkeysPerInterval 1000 
#MaxThreadsPerHotkey 1
#KeyHistory 50
SetWorkingDir %A_ScriptDir%

hotkeys_turned_on := true

Loop
{
    Sleep, 1000
    if WinActive("Grim Dawn", , "Grim Dawn ")
    {
        if hotkeys_turned_on
            continue
    }
    else if !hotkeys_turned_on
        continue
    
    hotkeys_turned_on ^= true
    Suspend, Toggle
}

~*LButton::
    KeyWait, LButton, T0.2 ;need to hold the button for 200ms to follow
    if ErrorLevel
    {         
        while GetKeyState("LButton", "P")
        {
            WinGetActiveStats, Title, Width, Height, X, Y
            MouseGetPos, xpos, ypos 
            xpos := xpos - Width/2 ;vector from the middle of the screen to the cursor
            ypos := Height/2 - ypos
            
            if xpos*xpos + ypos*ypos < 40000 ;<200 pix from center -> no rotation
                continue

            if (ypos > 0) and (Abs(ATan(xpos / ypos)) * 57.29578 < 20) 
                continue ; 20° degrees from 12 o'clock -> 40° no rotation sector
            
            if xpos > 0 ;right half of screen -> rotate right
            {
                Send {n down}
                Sleep, 100 ; haven't tested other values here
                Send {n up}
            }
            else ;left half of screen -> rotate left
            {
                Send {b down}
                Sleep, 100
                Send {b up}
            }
            
            Sleep, 20
        }
    }
Return

Sometimes people use something similar to

#IfWinActive ahk_class Grim Dawn

to make script only work in a particular application but such lines don’t work perfectly for me

Adding a specific hotkey to pause a script also work I think
Tab::Suspend

1 Like

Perfect, thanks.

Because you mentioned it in the other thread.
If people get dizzy it is mostly because they play at a low framerate (30 for me) and have motion blur disabled.
I play with nearly stable 144 fps and adaptive sync enabled but I think even 60 fps should be enough.
GD does not have the motion blur option so I guess a high framerate is the only solution.

You may be on to something but I personally just never get dizzy (in any real life situation) so I think that the genetic factor plays a role as well. In Grim Dawn I have 40 - 50 FPS

Also try playing Grim Dawn with a controller. It’s great. And you can use controller for fighting and mouse for shopping and the game dynamically switches between them. Rotating the camera with the right stick is great and you can configure everything in Steam Big Picture (for example I set my analog to activate Wind Devil whenever I touch it so it’s cast kinda automatically when I move my character with this analog stick)

I unfortunately don’t know how to use AutoHotkey with gamepads, it’s definitely not straightforward.

Didn’t know you can play GD with controller just that well. Will definitly try that out.

Have you tried to remap the xinput inputs to other keys which work with AHK?
Just an idea.
Also depends on controller for sure. For the steam controller that shouldn’t be a problem in general.

It should work but the syntaxis must be

#IfWinActive, Grim Dawn

all the text below this switch will be working only if Grim Dawn window is active.

I’m 90% sure I tested it before but it was not working 100% of the time or as expected and I moved to this ugly Loop as a consequence.

[edit] Ok, I’ve tested it and it works now. I’m keeping it for further testing.