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
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:
- Search radius — scan for the color in a circular area around the cursor (configurable size)?
- Drag behavior — should it continuously track and follow the color while held, or one-time drag to the color location?
- 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?
- Smoothness — should this be a spline/easing curve (S-curve, ease-in-out, etc.) or just gradual interpolation?
- 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
-- 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
endHow 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_mintospeed_maxusing theaccelparameter - 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
-- 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
endWhat 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
-- 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
endWhat 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:
- Search for initial target
- Lock and track that target
- Target disappears → wait
- Delay expires → search again
- Repeat