05-03-26

Path of Exile 2 QoL Script with Coroutines

This Lua script for Rebind hardware input automates quality-of-life features in Path of Exile 2, including hideout commands, party management, and item linking, using coroutines for improved timing.

rebindlua scriptpath of exile 2qolcoroutineshardware inputscript

Question

Rewrite this AHK script in Rebind Lua:

; Path of Exile 2 Quality of Life Script ; =================================== ; Features: ; - F5: Quick hideout command ; - F6: Thank for trade and leave party (%ty4t) ; - F7: Leave party ; - F11: Link item under cursor (RCtrl+RAlt+Click) ; - Ctrl + MouseWheel: Navigate left/right in stash tabs ; - Ctrl + Left Click (hold): Rapid-fire clicking for fast inventory actions ; =================================== ; Author: github.com/oeo (taky@taky.com) ; Version: Requires AutoHotkey v1.1.33.06

#Requires AutoHotkey v1.1 #NoEnv ; Recommended for performance and compatibility #SingleInstance force ; Only allow one instance of script to run

; Read configuration from config.ini IniRead, THANK_MESSAGE, config.ini, Settings, thank_message, ty4t IniRead, HIDEOUT_KEY, config.ini, Settings, hideout_key, {F5} IniRead, LEAVE_KEY, config.ini, Settings, leave_key, {F6} IniRead, THANK_LEAVE_KEY, config.ini, Settings, thank_leave_key, {F7} IniRead, LINK_ITEM_KEY, config.ini, Settings, link_item_key, {F11}

; Set script tray name Menu, Tray, Tip, poe2.ahk

; Set typing speed SetKeyDelay, 10

; Only activate these hotkeys when PoE2 window is focused #IfWinActive Path of Exile 2

; Register dynamic hotkeys Hotkey, %HIDEOUT_KEY%, Hideout Hotkey, %LEAVE_KEY%, LeaveParty Hotkey, %THANK_LEAVE_KEY%, ThankAndLeave Hotkey, %LINK_ITEM_KEY%, LinkItem

return ; End of auto-execute section

; Go to hideout Hideout: SendInput {Enter}/hideout{Enter} return

; Leave party LeaveParty: SendInput {Enter}/leave{Enter} return

; Thank and leave party ThankAndLeave: SendInput {Enter}%THANK_MESSAGE%{Enter} Sleep, 25 SendInput {Enter}/leave{Enter} return

; Link item under cursor LinkItem: SendInput {RControl down}{RAlt down}{Click}{RAlt up}{RControl up} return

; Navigates to previous stash tab ^WheelUp::Send, {Left}

; Navigates to next stash tab ^WheelDown::Send, {Right}

; Rapidly clicks while holding Ctrl + Left Click ~$^LButton:: While GetKeyState("LButton", "P") and GetKeyState("Ctrl", "P") { Click Sleep, 5 } return

#IfWinActive

Rebind AI

lua
-- rebind: min_sdk=0.1.0
-- rebind: name=Path of Exile 2 QoL
-- rebind: process=PathOfExile2.exe
-- rebind: window=Path of Exile 2

local cfg = UI.Schema({
    thank_message = UI.Text("ty4t", { label = "Thank Message", maxLength = 50 }),
    hideout_key = UI.Keybind("F5", { label = "Hideout Key", group = "Hotkeys" }),
    leave_key = UI.Keybind("F6", { label = "Leave Party Key", group = "Hotkeys" }),
    thank_leave_key = UI.Keybind("F7", { label = "Thank & Leave Key", group = "Hotkeys" }),
    link_item_key = UI.Keybind("F11", { label = "Link Item Key", group = "Hotkeys" }),
    click_speed = UI.Slider(5, { min = 1, max = 50, label = "Click Speed (ms)", group = "Advanced" }),
})

-- Hideout command
Bind(cfg.hideout_key, function()
    HID.Press("Enter")
    HID.Type("/hideout")
    HID.Press("Enter")
    return false
end)

-- Leave party
Bind(cfg.leave_key, function()
    HID.Press("Enter")
    HID.Type("/leave")
    HID.Press("Enter")
    return false
end)

-- Thank and leave party
Bind(cfg.thank_leave_key, function()
    HID.Press("Enter")
    HID.Type(cfg.thank_message)
    HID.Press("Enter")
    Sleep(25)
    HID.Press("Enter")
    HID.Type("/leave")
    HID.Press("Enter")
    return false
end)

-- Link item under cursor
Bind(cfg.link_item_key, function()
    HID.Down("RCtrl")
    HID.Down("RAlt")
    HID.Press("Mouse1")
    HID.Up("RAlt")
    HID.Up("RCtrl")
    return false
end)

-- Ctrl + Mouse Wheel Up: Previous stash tab
function OnScroll(delta)
    if Input.IsDown("LCtrl") then
        if delta > 0 then
            HID.Press("Left")
        else
            HID.Press("Right")
        end
        return false
    end
    return true
end

-- Ctrl + Left Click (hold): Rapid-fire clicking
function OnDown(key)
    if key == "Mouse1" and Input.IsDown("LCtrl") then
        Run(function()
            while Input.IsDown("Mouse1") and Input.IsDown("LCtrl") do
                HID.Press("Mouse1", 20)
                Sleep(cfg.click_speed)
            end
        end)
        return false
    end
    return true
end

function OnUp(key)
    if key == "Mouse1" and Input.IsDown("LCtrl") then
        return false
    end
    return true
end

function OnBlur()
    Audio.StopAll()
end

function OnStop()
    Audio.StopAll()
end

How it works:

  • UI Config: Settings are stored as toggles and sliders in the Rebind UI. Users can adjust keys and parameters without editing the script.
  • Hotkeys as Bind: Each AHK hotkey (F5-F7, F11) is replaced with a Bind() call that triggers the corresponding action.
  • Hideout/Leave/Thank+Leave: Sends the chat commands by pressing Enter, typing the message, and pressing Enter again.
  • Link Item: Holds RCtrl+RAlt, clicks the mouse, then releases modifiers in reverse order.
  • Stash Navigation: OnScroll() checks if LCtrl is held. Positive scroll (up) sends Left arrow, negative scroll (down) sends Right arrow.
  • Rapid-fire Click: When Ctrl+Mouse1 is pressed, a coroutine spawns that clicks repeatedly at the configured speed while both keys remain held. Returns false to block the original click event.
  • Cleanup: OnBlur() and OnStop() ensure no audio or tasks leak when the script is unloaded or the game loses focus.

The script is automatically targeted to the game via the process= modeline, so it only activates when the game is in focus. All key names use canonical Rebind names (e.g., F5, Mouse1, LCtrl, RCtrl, RAlt).

Learn more:

Question

Can we put those sequences into coroutines instead of calling HID.Press in a blocking way, same with type? or is the function returned by Bind already a coroutine?

Rebind AI

The function returned by Bind is not a coroutine — it's a synchronous callback. However, you can wrap it with Async() to automatically spawn a coroutine, which lets you use Sleep() inside.

Here's the rewritten version with coroutines for all the sequences:

lua
-- rebind: min_sdk=0.1.0
-- rebind: name=Path of Exile 2 QoL
-- rebind: process=PathOfExile2.exe
-- rebind: window=Path of Exile 2

local cfg = UI.Schema({
    thank_message = UI.Text("ty4t", { label = "Thank Message", maxLength = 50 }),
    hideout_key = UI.Keybind("F5", { label = "Hideout Key", group = "Hotkeys" }),
    leave_key = UI.Keybind("F6", { label = "Leave Party Key", group = "Hotkeys" }),
    thank_leave_key = UI.Keybind("F7", { label = "Thank & Leave Key", group = "Hotkeys" }),
    link_item_key = UI.Keybind("F11", { label = "Link Item Key", group = "Hotkeys" }),
    click_speed = UI.Slider(5, { min = 1, max = 50, label = "Click Speed (ms)", group = "Advanced" }),
})

-- Hideout command
Bind(cfg.hideout_key, Async(function()
    HID.Press("Enter")
    Sleep(50)
    HID.Type("/hideout", 15)
    Sleep(50)
    HID.Press("Enter")
end))

-- Leave party
Bind(cfg.leave_key, Async(function()
    HID.Press("Enter")
    Sleep(50)
    HID.Type("/leave", 15)
    Sleep(50)
    HID.Press("Enter")
end))

-- Thank and leave party
Bind(cfg.thank_leave_key, Async(function()
    HID.Press("Enter")
    Sleep(50)
    HID.Type(cfg.thank_message, 15)
    Sleep(50)
    HID.Press("Enter")
    Sleep(25)
    HID.Press("Enter")
    Sleep(50)
    HID.Type("/leave", 15)
    Sleep(50)
    HID.Press("Enter")
end))

-- Link item under cursor
Bind(cfg.link_item_key, function()
    HID.Down("RCtrl")
    HID.Down("RAlt")
    HID.Press("Mouse1")
    HID.Up("RAlt")
    HID.Up("RCtrl")
    return false
end)

-- Ctrl + Mouse Wheel Up: Previous stash tab
function OnScroll(delta)
    if Input.IsDown("LCtrl") then
        if delta > 0 then
            HID.Press("Left")
        else
            HID.Press("Right")
        end
        return false
    end
    return true
end

-- Ctrl + Left Click (hold): Rapid-fire clicking
function OnDown(key)
    if key == "Mouse1" and Input.IsDown("LCtrl") then
        Run(function()
            while Input.IsDown("Mouse1") and Input.IsDown("LCtrl") do
                HID.Press("Mouse1", 20)
                Sleep(cfg.click_speed)
            end
        end)
        return false
    end
    return true
end

function OnUp(key)
    if key == "Mouse1" and Input.IsDown("LCtrl") then
        return false
    end
    return true
end

function OnBlur()
    Audio.StopAll()
end

function OnStop()
    Audio.StopAll()
end

Key changes:

  1. Async() wrapper: Each sequence-based Bind now uses Async(function() ... end) so Sleep() calls work inside.
  2. HID.Type() with per-character delay: Added a second argument (15ms) for character-by-character timing, making text input feel more natural.
  3. Sleep() between steps: Added small delays between Press() and Type() calls to let the game process input between commands.
  4. Link item stays synchronous: This one doesn't need sleeping, so it remains a plain function.

How Async() works:

  • When you call a Bind handler wrapped in Async(), it immediately returns false (block) and spawns a Run() coroutine in the background.
  • Inside that coroutine, Sleep() is available and doesn't block anything else.
  • The return value inside the async body has no effect — the blocking decision was already made when the coroutine was spawned.

This gives you much better control over timing and makes the sequences feel more human-like.

Question

Great thanks so much it works wonders.

Rebind AI

You're welcome! Glad it's working great for you. If you need any more tweaks or want to add other features to the script, feel free to ask. Happy gaming! 🎮