05-03-26

Recoil Control Script with Macro Playback

This Lua script provides recoil compensation for hardware input, using Macro.Play for improved accuracy and drift compensation. It includes adjustable strength and playback speed for precise control.

rebindlua scriptrecoil controlmacrohardware inputcs2valorant

Question

Recoil pull-down with adjustable strength

Rebind AI

lua
-- rebind: min_sdk=0.1.0
-- rebind: name=Recoil Control
-- rebind: process=cs2.exe
-- rebind: process=valorant.exe

local cfg = UI.Schema({
    enabled = UI.Toggle(true, { label = "Enable", group = "General" }),
    strength = UI.Slider(100, { min = 0, max = 200, suffix = "%", label = "Recoil Strength", group = "General" }),
    smoothness = UI.Slider(0, { min = 0, max = 50, suffix = "%", label = "Smoothing", group = "General", tooltip = "Higher = smoother but laggier" }),
    toggle_key = UI.Keybind("F6", { label = "Toggle Key", group = "Hotkeys" }),
})

-- Recoil pattern: relative mouse movements over time
-- Tuned for CS2 spray patterns (vertical pull-down with slight horizontal correction)
local recoilPattern = {
    { x = 0, y = 2, delay = 15 },
    { x = 0, y = 2, delay = 15 },
    { x = -1, y = 3, delay = 15 },
    { x = 1, y = 3, delay = 15 },
    { x = 0, y = 2, delay = 15 },
    { x = -1, y = 4, delay = 15 },
    { x = 1, y = 3, delay = 15 },
    { x = -2, y = 3, delay = 15 },
    { x = 2, y = 3, delay = 15 },
    { x = 0, y = 2, delay = 15 },
}

local activeTask = nil

function OnStart()
    Log.Info("Recoil control ready. Strength=" .. cfg.strength .. "%, Smoothing=" .. cfg.smoothness .. "%")
end

function OnDown(key)
    if key == cfg.toggle_key then
        cfg.enabled = not cfg.enabled
        UI.Notify(cfg.enabled and "Recoil Control ON" or "Recoil Control OFF", cfg.enabled and "success" or "info")
        return false
    end

    if key == "Mouse1" and cfg.enabled then
        -- Cancel any existing task
        if activeTask and activeTask:IsRunning() then
            activeTask:Cancel()
        end

        -- Scale the pattern by configured strength
        local str = cfg.strength / 100
        local smooth = cfg.smoothness / 100
        local scaled = Math.Scale(recoilPattern, 1, str)

        activeTask = Run(function()
            local buffer = { x = 0, y = 0 }

            -- Loop the pattern while Mouse1 is held
            while Input.IsDown("Mouse1") do
                for _, step in ipairs(scaled) do
                    if not Input.IsDown("Mouse1") then
                        break
                    end

                    -- Apply smoothing via exponential averaging
                    if smooth > 0 then
                        buffer.x = buffer.x * smooth + step.x * (1 - smooth)
                        buffer.y = buffer.y * smooth + step.y * (1 - smooth)
                        HID.Move(buffer.x, buffer.y)
                    else
                        HID.Move(step.x, step.y)
                    end

                    Sleep(step.delay or 15)
                end
            end
        end)

        return false
    end

    return true
end

function OnUp(key)
    if key == "Mouse1" and activeTask and activeTask:IsRunning() then
        activeTask:Cancel()
        activeTask = nil
    end
    return true
end

function OnBlur()
    if activeTask and activeTask:IsRunning() then
        activeTask:Cancel()
        activeTask = nil
    end
end

function OnStop()
    if activeTask and activeTask:IsRunning() then
        activeTask:Cancel()
    end
end

This script applies automatic recoil compensation for tactical shooters like CS2 and Valorant.

How it works:

  • Defines a spray pattern of downward and side-to-side mouse movements
  • Multiplies the pattern by your configured Strength slider (0-200%), letting you dial in precisely how much compensation is applied
  • Smoothing reduces jitter by averaging movement over time (optional; 0% disables)
  • Activates only while Mouse1 is held, and automatically loops the spray pattern
  • Toggle on/off with F6 (configurable)
  • Targets CS2 and Valorant only — won't affect other applications
  • Cleans up tasks properly on window blur or script stop

Adjust Strength to match your sensitivity and spray pattern. Start at 100% and tune from there.

Learn more:

Question

Can you utilize Macro.Play so it can benefit from drift compensation?

Rebind AI

lua
-- rebind: min_sdk=0.1.0
-- rebind: name=Recoil Control (Macro)
-- rebind: process=cs2.exe
-- rebind: process=valorant.exe

local cfg = UI.Schema({
    enabled = UI.Toggle(true, { label = "Enable", group = "General" }),
    strength = UI.Slider(100, { min = 0, max = 200, suffix = "%", label = "Recoil Strength", group = "General" }),
    speed = UI.Slider(100, { min = 25, max = 400, suffix = "%", label = "Playback Speed", group = "General", tooltip = "Speed multiplier for macro playback" }),
    toggle_key = UI.Keybind("F6", { label = "Toggle Key", group = "Hotkeys" }),
})

-- Recoil pattern as a macro: downward pull with horizontal micro-corrections
-- Format: { x, y, delay } shorthand for mouse movements
local recoilPattern = {
    { x = 0, y = 2, delay = 15 },
    { x = 0, y = 2, delay = 15 },
    { x = -1, y = 3, delay = 15 },
    { x = 1, y = 3, delay = 15 },
    { x = 0, y = 2, delay = 15 },
    { x = -1, y = 4, delay = 15 },
    { x = 1, y = 3, delay = 15 },
    { x = -2, y = 3, delay = 15 },
    { x = 2, y = 3, delay = 15 },
    { x = 0, y = 2, delay = 15 },
}

local activeTask = nil
local activeMacro = nil

function OnStart()
    Log.Info("Recoil control ready. Strength=" .. cfg.strength .. "%, Speed=" .. cfg.speed .. "%")
end

function OnDown(key)
    if key == cfg.toggle_key then
        cfg.enabled = not cfg.enabled
        UI.Notify(cfg.enabled and "Recoil Control ON" or "Recoil Control OFF", cfg.enabled and "success" or "info")
        return false
    end

    if key == "Mouse1" and cfg.enabled then
        -- Stop any existing macro
        if activeMacro and activeMacro:IsPlaying() then
            activeMacro:Stop()
        end
        if activeTask and activeTask:IsRunning() then
            activeTask:Cancel()
        end

        -- Scale pattern by strength, then play with speed multiplier
        local str = cfg.strength / 100
        local spd = cfg.speed / 100
        local scaled = Math.Scale(recoilPattern, 1, str)

        activeTask = Run(function()
            -- Loop macro while Mouse1 is held
            while Input.IsDown("Mouse1") do
                activeMacro = Macro.Play(scaled, spd, "replace")
                activeMacro:Wait() -- wait for this cycle to finish
            end
            activeMacro = nil
        end)

        return false
    end

    return true
end

function OnUp(key)
    if key == "Mouse1" then
        if activeMacro and activeMacro:IsPlaying() then
            activeMacro:Stop()
            activeMacro = nil
        end
        if activeTask and activeTask:IsRunning() then
            activeTask:Cancel()
            activeTask = nil
        end
    end
    return true
end

function OnBlur()
    if activeMacro and activeMacro:IsPlaying() then
        activeMacro:Stop()
        activeMacro = nil
    end
    if activeTask and activeTask:IsRunning() then
        activeTask:Cancel()
        activeTask = nil
    end
end

function OnStop()
    Macro.StopAll()
    if activeTask and activeTask:IsRunning() then
        activeTask:Cancel()
    end
end

Key improvements using Macro.Play:

  • Hardware drift compensation: Macro.Play benefits from the relay's built-in sub-5px position validation when using absolute mouse mode, eliminating cumulative drift over long sprays
  • Cleaner playback: Macro engine handles timing and movement delivery more precisely than manual Sleep() loops
  • Speed control: The speed parameter lets you adjust playback rate (0.25x to 4x) without modifying the pattern itself
  • Looping: The Run() wrapper loops the macro seamlessly while Mouse1 is held, restarting each cycle via activeMacro:Wait()
  • Cancellation: Properly stops both the macro and coroutine on release or blur

The macro is scaled by Strength before playback, then played at the configured Speed multiplier. This gives you independent control over both compensation amount and execution timing.

Learn more: