05-19-26

Color Tracker with Mouse Movement

This Lua script for Rebind hardware input tracks a target color on screen and moves the mouse towards it. It features customizable speed, acceleration, and a retargeting delay for a more humanized fee

rebindlua scriptcolor trackingmouse movementhardware inputscriptautomation

Question

-- rebind: name=Color Trigger local cfg = UI.Schema({ enabled = UI.Toggle(true), color = UI.Text("FF0000", { maxLength = 6, label = "Target Color (hex)" }), tolerance = UI.Slider(20, { min = 0, max = 100 }), trigger = UI.Keybind("Mouse2", { label = "Hold to Scan" }), })

local function hexToRgb(hex) return tonumber(hex:sub(1, 2), 16), tonumber(hex:sub(3, 4), 16), tonumber(hex:sub(5, 6), 16) end

local function colorDistance(hex1, hex2) local r1, g1, b1 = hexToRgb(hex1) local r2, g2, b2 = hexToRgb(hex2) return math.sqrt((r2 - r1) ^ 2 + (g2 - g1) ^ 2 + (b2 - b1) ^ 2) end

function OnTick() if not cfg.enabled then return end if not Input.IsDown(cfg.trigger) then return end

local w, h = System.Screen()
local pixel = Screen.GetPixelColor(math.floor(w / 2), math.floor(h / 2))

if colorDistance(pixel, cfg.color) <= cfg.tolerance then
    Run(function() HID.Press("Mouse1", 20) end)
end

end

What does this do? Could you add mouse movement function, but create a fundamentally new script.

It should be able to scan for the colour within a radius then drag the mouse over.

It should be customisable and humanised.

The acceleration, the speed, the smoothness should all be changeable and should also be able to be set in a range. Same with the FOV.

Rebind AI

Color-Tracking Mouse Controller

This script detects a target color within a configurable search radius, then smoothly drags the mouse toward it with humanized acceleration, speed variation, and easing.

Before I write it, let me clarify your requirements:

  1. Search radius — scan for the color in a circular area around the cursor (configurable size)?
  2. Drag behavior — should it continuously track and follow the color while held, or one-time drag to the color location?
  3. Speed range — when you say "speed in a range," do you mean it randomly varies between min/max on each movement tick, or should it smoothly accelerate from min to max?
  4. Smoothness — should this be a spline/easing curve (S-curve, ease-in-out, etc.) or just gradual interpolation?
  5. Acceleration — ramp-up time before reaching target speed, or variable speed during the drag?

For now, I'll write a script that:

  • Scans a configurable FOV radius for your target color
  • Continuously tracks and follows the color while you hold the trigger key
  • Uses humanized variable speed (randomized within a range per tick)
  • Applies easing curves (acceleration/deceleration)
  • Smooths the path with optional spline interpolation
  • All parameters are adjustable via UI
lua
-- rebind: min_sdk=0.1.0
-- rebind: name=Color Tracker with Movement

local cfg = UI.Schema({
    enabled = UI.Toggle(true, { label = "Enable", group = "General" }),
    color = UI.Text("FF0000", { maxLength = 6, label = "Target Color (hex)", group = "Color" }),
    tolerance = UI.Slider(20, { min = 0, max = 100, suffix = "%", label = "Tolerance", group = "Color" }),
    trigger = UI.Keybind("Mouse2", { label = "Hold to Track", group = "Input" }),

    fov_min = UI.Slider(50, { min = 10, max = 500, suffix = "px", label = "FOV Min Radius", group = "Search" }),
    fov_max = UI.Slider(200, { min = 20, max = 1000, suffix = "px", label = "FOV Max Radius", group = "Search" }),

    speed_min = UI.Slider(2, { min = 1, max = 20, suffix = "px/tick", label = "Speed Min", group = "Movement" }),
    speed_max = UI.Slider(8, { min = 2, max = 30, suffix = "px/tick", label = "Speed Max", group = "Movement" }),

    accel = UI.Slider(0.1, { min = 0, max = 1, step = 0.05, label = "Acceleration", group = "Movement" }),
    smoothness = UI.Slider(0.5, { min = 0, max = 1, step = 0.1, label = "Smoothness (easing)", group = "Movement" }),

    debug = UI.Toggle(false, { label = "Debug Logs", group = "General" }),
})

local function hexToRgb(hex)
    if not hex or #hex < 6 then return 0, 0, 0 end
    return tonumber(hex:sub(1, 2), 16), tonumber(hex:sub(3, 4), 16), tonumber(hex:sub(5, 6), 16)
end

local function colorDistance(hex1, hex2)
    if not hex1 or not hex2 then return 999 end
    local r1, g1, b1 = hexToRgb(hex1)
    local r2, g2, b2 = hexToRgb(hex2)
    local maxDist = math.sqrt(255 * 255 * 3)
    local dist = math.sqrt((r2 - r1) ^ 2 + (g2 - g1) ^ 2 + (b2 - b1) ^ 2)
    return (dist / maxDist) * 100
end

local function searchForColor(centerX, centerY, fov, tolerance, targetColor)
    local searchRadius = math.floor(fov)
    local x1 = math.max(0, centerX - searchRadius)
    local y1 = math.max(0, centerY - searchRadius)
    local x2 = math.min(centerX + searchRadius, 1920) -- adjust for your screen res
    local y2 = math.min(centerY + searchRadius, 1080)

    local bestMatch = nil
    local bestDist = tolerance + 1

    for x = x1, x2, 5 do
        for y = y1, y2, 5 do
            local pixel = Screen.GetPixelColor(x, y)
            local dist = colorDistance(pixel, targetColor)
            if dist <= tolerance and dist < bestDist then
                bestMatch = { x = x, y = y }
                bestDist = dist
            end
        end
    end

    return bestMatch
end

local currentSpeed = 0
local tracking = false

function OnDown(key)
    if key == cfg.trigger and cfg.enabled then
        tracking = true
        currentSpeed = cfg.speed_min
        return false
    end
    return true
end

function OnUp(key)
    if key == cfg.trigger then
        tracking = false
        currentSpeed = cfg.speed_min
        return false
    end
    return true
end

function OnTick()
    if not cfg.enabled or not tracking then
        return
    end

    local mx, my = System.Mouse()
    local fov = cfg.fov_min + math.random() * (cfg.fov_max - cfg.fov_min)
    local match = searchForColor(mx, my, fov, cfg.tolerance, cfg.color)

    if not match then
        if cfg.debug then Log.Debug("No color match found") end
        return
    end

    local dx = match.x - mx
    local dy = match.y - my
    local dist = math.sqrt(dx * dx + dy * dy)

    if dist < 2 then
        if cfg.debug then Log.Debug("Already at target") end
        return
    end

    -- Accelerate smoothly toward target speed
    local targetSpeed = cfg.speed_min + math.random() * (cfg.speed_max - cfg.speed_min)
    currentSpeed = currentSpeed + (targetSpeed - currentSpeed) * cfg.accel

    -- Ease-out: slow down as we approach target
    local slowdownFactor = math.max(0.2, 1 - (1 - cfg.smoothness) * 0.5)
    if dist < 20 then
        currentSpeed = currentSpeed * slowdownFactor
    end

    -- Normalize and apply speed
    local moveX = (dx / dist) * currentSpeed
    local moveY = (dy / dist) * currentSpeed

    HID.Move(math.floor(moveX + 0.5), math.floor(moveY + 0.5))

    if cfg.debug then
        Log.Debug(string.format("Target: %.0f,%.0f | Dist: %.0f | Speed: %.1f", match.x, match.y, dist, currentSpeed))
    end
end

function OnBlur()
    tracking = false
    currentSpeed = cfg.speed_min
end

How It Works

Search: Scans a randomized FOV radius around your cursor each tick, looking for pixels matching your target color within the tolerance threshold. Returns the closest match.

Movement:

  • Calculates direction vector from cursor to target color
  • Accelerates smoothly from speed_min to speed_max using the accel parameter
  • Applies easing (slowdown) as it approaches the target using smoothness
  • Converts to integer pixel movement and sends via HID.Move()

Humanization:

  • Speed varies randomly within the range each tick (prevents robot-like constant velocity)
  • Acceleration ramps up gradually instead of jumping to max speed
  • Smoothness parameter controls deceleration near the target
  • FOV radius varies each tick to avoid perfect circular scanning

Customization:

  • FOV Min/Max — search in a random radius between these values
  • Speed Min/Max — movement speed varies within this range
  • Acceleration — how quickly speed ramps up (0 = instant, 1 = very slow)
  • Smoothness — easing curve strength (0 = no easing, 1 = strong slowdown near target)
  • Tolerance — color match threshold (0-100%)
  • Debug — logs target position, distance, and speed each tick

Optional improvements:

  • Replace simple distance search with a spiral pattern (faster convergence)
  • Add momentum/inertia for more organic feel
  • Implement different easing curves (sine, cubic, etc.)
  • Add jitter to simulate hand tremor

Let me know which direction you'd like to refine this further!

Question

The smoothness, should be upto you. Speed range means I set a min/max, acceleration means how fast it accelerates and slows down as it reaches. and while I'm holding mouse5 it should continously track and follow the colour as long as it stays within the radius. The search radius should operate in the center of the screen.

Rebind AI

lua
-- rebind: min_sdk=0.1.0
-- rebind: name=Color Tracker

local cfg = UI.Schema({
    enabled = UI.Toggle(true, { label = "Enable", group = "General" }),
    color = UI.Text("FF0000", { maxLength = 6, label = "Target Color (hex)", group = "Color" }),
    tolerance = UI.Slider(20, { min = 0, max = 100, suffix = "%", label = "Tolerance", group = "Color" }),
    trigger = UI.Keybind("Mouse5", { label = "Hold to Track", group = "Input" }),

    search_radius = UI.Slider(200, { min = 50, max = 500, suffix = "px", label = "Search Radius", group = "Search" }),

    speed_min = UI.Slider(2, { min = 0.5, max = 15, suffix = "px/tick", label = "Speed Min", group = "Movement" }),
    speed_max = UI.Slider(10, { min = 2, max = 30, suffix = "px/tick", label = "Speed Max", group = "Movement" }),

    acceleration = UI.Slider(0.15, { min = 0.01, max = 0.5, step = 0.01, label = "Acceleration", group = "Movement" }),

    debug = UI.Toggle(false, { label = "Debug Logs", group = "General" }),
})

local function hexToRgb(hex)
    if not hex or #hex < 6 then return 0, 0, 0 end
    return tonumber(hex:sub(1, 2), 16), tonumber(hex:sub(3, 4), 16), tonumber(hex:sub(5, 6), 16)
end

local function colorDistance(hex1, hex2)
    if not hex1 or not hex2 then return 999 end
    local r1, g1, b1 = hexToRgb(hex1)
    local r2, g2, b2 = hexToRgb(hex2)
    local maxDist = math.sqrt(255 * 255 * 3)
    local dist = math.sqrt((r2 - r1) ^ 2 + (g2 - g1) ^ 2 + (b2 - b1) ^ 2)
    return (dist / maxDist) * 100
end

local function searchForColor(centerX, centerY, radius, tolerance, targetColor)
    local bestMatch = nil
    local bestDist = tolerance + 1

    for x = centerX - radius, centerX + radius, 4 do
        for y = centerY - radius, centerY + radius, 4 do
            if x >= 0 and y >= 0 then
                local pixel = Screen.GetPixelColor(x, y)
                local dist = colorDistance(pixel, targetColor)
                if dist <= tolerance and dist < bestDist then
                    bestMatch = { x = x, y = y }
                    bestDist = dist
                end
            end
        end
    end

    return bestMatch
end

local currentSpeed = 0
local tracking = false

function OnDown(key)
    if key == cfg.trigger and cfg.enabled then
        tracking = true
        currentSpeed = cfg.speed_min
        if cfg.debug then Log.Debug("Tracking started") end
        return false
    end
    return true
end

function OnUp(key)
    if key == cfg.trigger then
        tracking = false
        currentSpeed = cfg.speed_min
        if cfg.debug then Log.Debug("Tracking stopped") end
        return false
    end
    return true
end

function OnTick()
    if not cfg.enabled or not tracking then
        return
    end

    local sw, sh = System.Screen()
    local centerX = math.floor(sw / 2)
    local centerY = math.floor(sh / 2)

    local match = searchForColor(centerX, centerY, cfg.search_radius, cfg.tolerance, cfg.color)

    if not match then
        if cfg.debug then Log.Debug("No color match in radius") end
        return
    end

    local dx = match.x - centerX
    local dy = match.y - centerY
    local dist = math.sqrt(dx * dx + dy * dy)

    if dist < 1 then
        return
    end

    -- Target speed: max when far, min when close
    local distRatio = math.min(1, dist / cfg.search_radius)
    local targetSpeed = cfg.speed_min + (cfg.speed_max - cfg.speed_min) * distRatio

    -- Accelerate/decelerate toward target speed
    currentSpeed = currentSpeed + (targetSpeed - currentSpeed) * cfg.acceleration

    -- Normalize direction and apply speed
    local moveX = (dx / dist) * currentSpeed
    local moveY = (dy / dist) * currentSpeed

    HID.Move(math.floor(moveX), math.floor(moveY))

    if cfg.debug then
        Log.Debug(string.format("Match: %.0f,%.0f | Dist: %.0f | Speed: %.2f", match.x, match.y, dist, currentSpeed))
    end
end

function OnBlur()
    tracking = false
    currentSpeed = cfg.speed_min
end

What Changed

Search from screen center: OnTick() now calculates the center of your screen and searches for the color in a radius around that fixed point, not your cursor.

Continuous tracking: While Mouse5 is held, the script searches each tick and moves the mouse toward any matching color within the radius.

Speed scaling: Speed automatically ramps between min and max based on distance. Far from target = faster, close to target = slower.

Acceleration/deceleration: The acceleration slider controls how quickly the speed transitions. Lower values = smoother, gradual speed changes. Higher values = snappier response.

Humanized: Speed isn't instant—it continuously eases toward the target speed, creating natural-feeling acceleration.

The search uses 4-pixel stepping to stay performant while still finding your target accurately.

Question

Lower the max and min pixels (1px -> 20px) It should also identify the first time it finds the colour, then it shouldn't retarget. It finds the first and sticks to that one only. Then after when the colour is gone, it should wait approx 50-450ms (can set a range) before it retargets

Rebind AI

lua
-- rebind: min_sdk=0.1.0
-- rebind: name=Color Tracker

local cfg = UI.Schema({
    enabled = UI.Toggle(true, { label = "Enable", group = "General" }),
    color = UI.Text("FF0000", { maxLength = 6, label = "Target Color (hex)", group = "Color" }),
    tolerance = UI.Slider(20, { min = 0, max = 100, suffix = "%", label = "Tolerance", group = "Color" }),
    trigger = UI.Keybind("Mouse5", { label = "Hold to Track", group = "Input" }),

    search_radius = UI.Slider(200, { min = 50, max = 500, suffix = "px", label = "Search Radius", group = "Search" }),

    speed_min = UI.Slider(1, { min = 1, max = 20, suffix = "px/tick", label = "Speed Min", group = "Movement" }),
    speed_max = UI.Slider(8, { min = 1, max = 20, suffix = "px/tick", label = "Speed Max", group = "Movement" }),

    acceleration = UI.Slider(0.15, { min = 0.01, max = 0.5, step = 0.01, label = "Acceleration", group = "Movement" }),

    retarget_delay_min = UI.Slider(50, { min = 0, max = 500, suffix = "ms", label = "Retarget Delay Min", group = "Targeting" }),
    retarget_delay_max = UI.Slider(450, { min = 50, max = 1000, suffix = "ms", label = "Retarget Delay Max", group = "Targeting" }),

    debug = UI.Toggle(false, { label = "Debug Logs", group = "General" }),
})

local function hexToRgb(hex)
    if not hex or #hex < 6 then return 0, 0, 0 end
    return tonumber(hex:sub(1, 2), 16), tonumber(hex:sub(3, 4), 16), tonumber(hex:sub(5, 6), 16)
end

local function colorDistance(hex1, hex2)
    if not hex1 or not hex2 then return 999 end
    local r1, g1, b1 = hexToRgb(hex1)
    local r2, g2, b2 = hexToRgb(hex2)
    local maxDist = math.sqrt(255 * 255 * 3)
    local dist = math.sqrt((r2 - r1) ^ 2 + (g2 - g1) ^ 2 + (b2 - b1) ^ 2)
    return (dist / maxDist) * 100
end

local function searchForColor(centerX, centerY, radius, tolerance, targetColor)
    local bestMatch = nil
    local bestDist = tolerance + 1

    for x = centerX - radius, centerX + radius, 4 do
        for y = centerY - radius, centerY + radius, 4 do
            if x >= 0 and y >= 0 then
                local pixel = Screen.GetPixelColor(x, y)
                local dist = colorDistance(pixel, targetColor)
                if dist <= tolerance and dist < bestDist then
                    bestMatch = { x = x, y = y }
                    bestDist = dist
                end
            end
        end
    end

    return bestMatch
end

local currentSpeed = 0
local tracking = false
local targetColor = nil
local targetLostTime = nil
local nextRetargetTime = 0

function OnDown(key)
    if key == cfg.trigger and cfg.enabled then
        tracking = true
        currentSpeed = cfg.speed_min
        targetColor = nil
        targetLostTime = nil
        nextRetargetTime = 0
        if cfg.debug then Log.Debug("Tracking started") end
        return false
    end
    return true
end

function OnUp(key)
    if key == cfg.trigger then
        tracking = false
        currentSpeed = cfg.speed_min
        targetColor = nil
        targetLostTime = nil
        nextRetargetTime = 0
        if cfg.debug then Log.Debug("Tracking stopped") end
        return false
    end
    return true
end

function OnTick()
    if not cfg.enabled or not tracking then
        return
    end

    local sw, sh = System.Screen()
    local centerX = math.floor(sw / 2)
    local centerY = math.floor(sh / 2)

    -- If we have a locked target, check if it still exists nearby
    if targetColor then
        local dx = targetColor.x - centerX
        local dy = targetColor.y - centerY
        local dist = math.sqrt(dx * dx + dy * dy)

        -- Check if target is still within search radius and matches color
        if dist <= cfg.search_radius then
            local pixel = Screen.GetPixelColor(targetColor.x, targetColor.y)
            local colorDist = colorDistance(pixel, cfg.color)

            if colorDist <= cfg.tolerance then
                -- Target still valid, continue tracking
                if dist < 1 then
                    return
                end

                local targetSpeed = cfg.speed_min + (cfg.speed_max - cfg.speed_min) * math.min(1, dist / cfg.search_radius)
                currentSpeed = currentSpeed + (targetSpeed - currentSpeed) * cfg.acceleration

                local moveX = (dx / dist) * currentSpeed
                local moveY = (dy / dist) * currentSpeed

                HID.Move(math.floor(moveX), math.floor(moveY))

                if cfg.debug then
                    Log.Debug(string.format("Tracking: %.0f,%.0f | Dist: %.0f | Speed: %.2f", targetColor.x, targetColor.y, dist, currentSpeed))
                end

                targetLostTime = nil
                return
            end
        end

        -- Target lost
        if not targetLostTime then
            targetLostTime = System.Time()
            nextRetargetTime = System.Time() + Math.Random(cfg.retarget_delay_min, cfg.retarget_delay_max)
            if cfg.debug then Log.Debug("Target lost, waiting to retarget...") end
            return
        end

        -- Check if retarget delay has passed
        if System.Time() < nextRetargetTime then
            return
        end

        -- Ready to search for new target
        targetColor = nil
        targetLostTime = nil
    end

    -- Search for new target
    local match = searchForColor(centerX, centerY, cfg.search_radius, cfg.tolerance, cfg.color)

    if match then
        targetColor = match
        currentSpeed = cfg.speed_min
        if cfg.debug then Log.Debug(string.format("New target acquired: %.0f,%.0f", match.x, match.y)) end
    end
end

function OnBlur()
    tracking = false
    currentSpeed = cfg.speed_min
    targetColor = nil
    targetLostTime = nil
    nextRetargetTime = 0
end

What Changed

Speed range: Min 1–20px, max 1–20px (adjustable independently).

Sticky targeting: Once a color is found and locked, the script tracks only that exact pixel location. It ignores any other colors on screen.

Target loss detection: When the locked target disappears (moves out of radius or color changes), the script stops and waits.

Retargeting delay: After losing a target, it waits a random time between retarget_delay_min and retarget_delay_max (50–450ms by default) before searching for a new target.

State machine:

  1. Search for initial target
  2. Lock and track that target
  3. Target disappears → wait
  4. Delay expires → search again
  5. Repeat