AutoHotKey HotBar Manager v2

FR0GG3R

Member
I've updated Amundir/Taplar original AutoHotKey HotBar Manager script as AFAIK he is no longer active.
This version is compatible with AutoHotKey v2 and has a couple of tiny enhancements.
This script is written for DDO with hotbar actions in mind.
Maybe you are a cleave machine that cycles through cleave, great cleave, momentum swing, lay waste, and other skills that grant extra +W damage when used.
Maybe you are a spell caster who has certain spells you like to cycle through.
Or maybe you have skills that can buff you, but have to be renewed after a certain amount of time.

In those cases, managing the cooldowns on the skills is important. This script is intended to give you an extra tool towards improving this management. You get to define what actions you want to execute, in what order they should execute, and tell it how long the action will be on cooldown before it can be used again. The script will look from top to bottom for the first action that is not on cooldown any longer and will execute it once again.

How to use:
  • Download AutoHotKey and install.
  • Copy the script below into a text editor like notepad.
  • The script includes comments on values you can modify.
    • The only part you need to modify is ; Define your hotbar actions here section to the hotkeys you want activated and their cooldowns.
  • Save as a file with a .ahk extension (not .txt) eg hotkeymanager.ahk
  • Double-click the saved script to launch AutoHotKey, you should see a green H in the system tray.
    • You may need to right-click and Run as administrator to work correctly with DDO.
  • Enable/Disable the script in DDO by pressing the pause key.
  • When enabled the script kicks in after one-second of swinging. Modify by changing TheDelayBeforeStarting value.

Code:
#Requires AutoHotkey v2.0
#SingleInstance Force

; Initialize global variables
global TheScriptIsEnabled := false
global WhenTheLeftMouseButtonWasClicked := 0
global HotbarActions := []

; PURPOSE
; * The purpose of this script is to help manage the use of common actions performed by a
;   character during battle, by executing skills as they come off cooldown as soon as
;   they are available.
; * To toggle the script enabled/disabled, simply hit the `Pause` button on your keyboard.
; * To terminate the script, hit Shift+Esc.
; * When enabled, the script will wait for the user to hold down the left mouse button for
;   a specified amount of time before beginning to cycle through the defined actions.
;END;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;CHARACTER SETTINGS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; * Edit the following to customize the script for your character.
; * The order of hotbar actions is the order in which the script will try to execute
;   them, so put them in whatever order you feel will maximize your experience.
; * You can include all or none of the optional variables when defining your hotbar
;   actions.  The variables between the `{}` can be in any order you wish.
;
; The following is the amount of time (in milliseconds) that the script will wait after
; you have begun to hold down your left mouse button before starting to cycle through the
; hotbar actions.  This is to try to help distinguish between actual battle vs simple
; single clicks.

global TheDelayBeforeStarting := 1000

; The following specifies your primary hotbar.  This is the hotbar you typically keep in
; focus while you play.  If you are not sure which one this is, when you hit a number it
; should try to execute one of your actions on one of your hotbars.  The hotbar that
; contains the action executed by you hitting a number is your primary bar.  The primary
; bar will be switched to each time the script starts evaluating the held left mouse
; button and, in the case that a defined hotbar action specifies that it is on another
; hotbar, the primary hotbar will be switched back to after that action has been executed.

global PrimaryHotbar := 1

; Most actions have some sort of animation that happens before the next action can start.
; Outside of the script this is not an issue, but with the script trying to keep track of
; when an action was last executed, actions being delayed due to stacking of animations
; can potentially be an issue.  To address this the script will wait the following amount
; of time (in milliseconds) before trying to find the next action to execute.  Adjust this
; if the majority of your animations are longer/shorter.

global AnimationDelay := 700

; The following details the options for defining a hotbar action.
; Action[required]   - The number you would normally hit to activate the action on the
;     hotbar.
; Cooldown[required] - The amount of time (in milliseconds) the action must wait before
;     being executed again.
; Hotbar[optional]   - The hotbar that the action is on.
; Delay[optional]    - The amount of time (in milliseconds) to add to the AnimationDelay
;     after executing an action to allow for the animation to finish.  A negative value
;     will reduce the delay.
; LastUsed[required] - A variable used to keep track of when the action was last executed.
;     This should always be zero.

; Example - copy, paste, and edit the text after the `;` to define a hotbar action
;HotbarActions.Push({ action: 2, cooldown: 7000, hotbar: 2, delay: 200, lastUsed: 0 })
; Example - you can also send non-hotbar commands, such as the left alt button for firing a rune arm
;HotbarActions.Push({ action: "LAlt", cooldown: 3000, delay: -500, lastUsed: 0})


; Define your hotbar actions here
HotbarActions.Push({ action: 0, cooldown: 3000, lastUsed: 0 })
HotbarActions.Push({ action: 6, cooldown: 3000, lastUsed: 0 })
HotbarActions.Push({ action: 7, cooldown: 10000, lastUsed: 0 })
HotbarActions.Push({ action: 8, cooldown: 15000, lastUsed: 0 })
HotbarActions.Push({ action: 7, cooldown: 15000, hotbar: 6, lastUsed: 0 })

;END;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Do not edit any of the following unless you know what you are doing, \,,/(^_^)\,,/
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Enable/Disable the script with audio indicator
*~Pause:: {
    global TheScriptIsEnabled
    TheScriptIsEnabled := !TheScriptIsEnabled
    if TheScriptIsEnabled {
        SoundBeep(1500, 150)  ; Beep sound to indicate script is enabled
    } else {
        SoundBeep(1000, 150)  ; Beep sound to indicate script is disabled
    }
    Return
}

; Terminate the script
+Escape:: {
    ExitApp
    Return
}

#HotIf WinActive("Dungeons and Dragons Online")
*~LButton:: {
    global TheScriptIsEnabled, WhenTheLeftMouseButtonWasClicked
    if TheScriptIsEnabled {
        WhenTheLeftMouseButtonWasClicked := A_TickCount
        LoopActionsWhileLeftMouseButtonIsClicked()
    }
    Return
}
#HotIf

; FUNCTIONS

LoopActionsWhileLeftMouseButtonIsClicked() {
    global TheDelayBeforeStarting, WhenTheLeftMouseButtonWasClicked, PrimaryHotbar, AnimationDelay
    SleepInterval := 10  ; Start with a small sleep interval
    while GetKeyState("LButton", "P") {
        if WhenTheLeftMouseButtonWasClicked + TheDelayBeforeStarting < A_TickCount {
            SwitchToHotbar(PrimaryHotbar)
            DelayAfterAction := ExecuteFirstActionNotOnCooldownAndReturnDelay()
            if DelayAfterAction is Number {
                Sleep(DelayAfterAction)
            } else {
                Sleep(AnimationDelay)
            }
            SleepInterval := 10  ; Reset sleep interval after performing an action
        } else {
            ; Increase sleep duration slightly if there's no action, up to a max (e.g., 50 ms)
            Sleep(SleepInterval)
            SleepInterval := Min(SleepInterval + 1, 50)
        }
    }
    Return
}

ExecuteFirstActionNotOnCooldownAndReturnDelay() {
    global AnimationDelay, HotbarActions, PrimaryHotbar
    DelayAfterLoopFinishes := AnimationDelay
    static ShouldSwitchBackToPrimaryBar := false  ; Static variable to retain state across calls

    for index, actionObj in HotbarActions {
        if actionObj.lastUsed + actionObj.cooldown < A_TickCount {
            if actionObj.HasOwnProp("delay") {
                totalDelay := AnimationDelay + actionObj.delay
                DelayAfterLoopFinishes := totalDelay >= 0 ? totalDelay : 0
            }

            if actionObj.HasOwnProp("hotbar") {
                SwitchToHotbar(actionObj.hotbar)
                ShouldSwitchBackToPrimaryBar := true
            }

            action := actionObj.action
            ; Send the action key
            if action is Number {
                SendInput(action)
            } else {
                SendInput("{" action "}")
            }

            actionObj.lastUsed := A_TickCount

            if ShouldSwitchBackToPrimaryBar {
                SwitchToHotbar(PrimaryHotbar)
                ShouldSwitchBackToPrimaryBar := false  ; Reset after switching back
            }

            break
        }
    }

    return DelayAfterLoopFinishes
}

SwitchToHotbar(HotBarNumber) {
    SendInput("{Blind}^" HotBarNumber)
}
 
Last edited:
Top