Cheat Engine Forum Index Cheat Engine
The Official Site of Cheat Engine
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 


Multi-Clicker Module: Smart Background Automation

 
Post new topic   Reply to topic    Cheat Engine Forum Index -> Cheat Engine Tutorials -> LUA Tutorials
View previous topic :: View next topic  
Author Message
AylinCE
Grandmaster Cheater Supreme
Reputation: 39

Joined: 16 Feb 2017
Posts: 1569

PostPosted: Sun Apr 05, 2026 2:48 pm    Post subject: Multi-Clicker Module: Smart Background Automation Reply with quote

I'll give many details below, but in summary:
Open the game on your desktop, configure and run the module, and while it clicks on the game screen in the background, you can freely use your mouse and desktop.


Overview
The AylinCE Multi-Clicker is a high-precision background automation tool designed for games that require repetitive clicking in non-active windows.

Unlike standard macro recorders that rely on erratic sleep timers, this module uses a Synchronized Unit-Counter system to ensure 100% timing accuracy between recording and playback.

Key Features
Smart Window Capture: Automatically filters top-level windows by PID and visibility to find the correct game handle, even if multiple processes are running.

Unit-Sync Timing:
Uses a "Metronome" logic (100ms beats).
Whether you record at a fast or slow pace, the playback engine stays perfectly in sync with the recorded delays.

Silent Background Clicks:
Utilizes PostMessageA to send clicks directly to the target window, allowing you to use your mouse for other tasks while the bot runs in the background.

Built-in Loop & Undo:
Features a dedicated loop counter and a "Clear Last Line" function for quick editing during live recording sessions.

Safety Lock:
A "Lock System" toggle that prevents accidental setting changes while the automation is active.

How it Works
Scan:
Enter the process name (e.g., game.exe) and hit Scan to lock the target HWND.

Record (F10):
Press F10 to capture your mouse coordinates.
The module automatically calculates the delay between clicks based on the internal tick counter.

Configure:
Set your desired loop count in the Settings panel.

Lock & Execute (FCool:
Toggle "Lock System" to enable the execution hotkey.
Press F8 to start/stop the automated sequence.

Technical Note
The module is designed to be CPU-friendly by avoiding heavy string parsing inside the high-frequency timer. It uses a "Flag-based" line reader (readLine) to ensure the script only processes data when a timing threshold is met.

Module:
Code:
--[[
    AylinCE - Multi-Clicker Module
    Targeting: Game Modding & Background Click Automation
]]--

--------------------------------------------------------------------------------
-- CORE MODULE: SYSTEM FUNCTIONS
--------------------------------------------------------------------------------

-- Get all PIDs matching the executable name
function checkAllPids(exeName)
    local sl = createStringList()
    getProcesslist(sl)
    local pids = {}
    for i = 0, sl.count - 1 do
        local text = sl[i]
        local pidHex = string.sub(text, 1, 8)
        local name = string.sub(text, 10)
        if name:lower() == exeName:lower() then
            pids[tonumber(pidHex, 16)] = true
        end
    end
    sl.Destroy()
    return pids
end

-- Smart Window Scanner (Filters by PID and Visibility)
function getHandleSmart(targetExe)
    local foundPids = checkAllPids(targetExe)
    local hwnd = executeCodeLocalEx("user32.GetTopWindow", 0)

    while hwnd and hwnd ~= 0 do
        local pid = getWindowProcessID(hwnd)
        if foundPids[pid] then
            local visible = executeCodeLocalEx("user32.IsWindowVisible", hwnd)
            if visible == 1 then
                local caption = getWindowCaption(hwnd)
                local className = getWindowClassName(hwnd)
                -- Ignore system/IME windows and empty captions
                if caption ~= "" and caption ~= "Default IME" and className ~= "IME" then
                    return hwnd
                end
            end
        end
        hwnd = executeCodeLocalEx("user32.GetWindow", hwnd, 2) -- 2 = GW_HWNDNEXT
    end
    return 0
end

-- Silent Click (Background PostMessage)
local function SilentClick(hwnd, x, y)
    if not hwnd or hwnd == 0 then
        showMessage("ERROR: Invalid HWND!")
        return false
    end
    local WM_LBUTTONDOWN, WM_LBUTTONUP, MK_LBUTTON = 0x0201, 0x0202, 0x0001
    local lParam = (y * 65536) + (x % 65536)
    local res1 = executeCodeLocalEx("user32.PostMessageA", hwnd, WM_LBUTTONDOWN, MK_LBUTTON, lParam)
    local res2 = executeCodeLocalEx("user32.PostMessageA", hwnd, WM_LBUTTONUP, 0, lParam)
    return (res1 ~= 0 and res2 ~= 0)
end


-- GUI STATE & TIMERS
Code:
if mcmfrm then mcmfrm.Destroy() mcmfrm=nil end
DP1=getScreenDPI()/96
mcmfrm=createForm()
mcmfrm.height=335*DP1 mcmfrm.width=400*DP1 mcmfrm.left=146*DP1 mcmfrm.top=134*DP1
mcmfrm.PopupMode=0 mcmfrm.caption="AylinCE - Multi-Clicker Module"
mcmfrm.Position="poDesktopCenter" mcmfrm.ShowInTaskBar="stAlways"
mcmfrm.BorderStyle="bsNone"
mcmfrm.setLayeredAttributes(0x000100, 255, LWA_COLORKEY | LWA_ALPHA )
mcmfrm.Color=0x000100

-------------------------
local aclk2 = {}
----------------------- aclk2.mpnl1 ----- 
aclk2.mpnl1=createPanel(mcmfrm)
aclk2.mpnl1.AutoSize=false
aclk2.mpnl1.height=40*DP1 aclk2.mpnl1.width=390*DP1 aclk2.mpnl1.left=5*DP1 aclk2.mpnl1.top=5*DP1
aclk2.mpnl1.caption="Multi-Clicker Module"
aclk2.mpnl1.Color=4694083
aclk2.mpnl1.Font.Style="fsBold" aclk2.mpnl1.Font.Size=14*DP1
aclk2.mpnl1.OnMouseDown=function() mcmfrm.DragNow() end
-----------------------
----------------------- aclk2.mbtn1 ----- 
aclk2.mbtn1=createButton(aclk2.mpnl1)
aclk2.mbtn1.AutoSize=false
aclk2.mbtn1.height=30*DP1 aclk2.mbtn1.width=30*DP1 aclk2.mbtn1.left=5*DP1 aclk2.mbtn1.top=5*DP1
aclk2.mbtn1.caption="_"
aclk2.mbtn1.Font.Style="fsBold" aclk2.mbtn1.Font.Size=12*DP1
-----------------------
----------------------- aclk2.mbtn2 ----- 
aclk2.mbtn2=createButton(aclk2.mpnl1)
aclk2.mbtn2.AutoSize=false
aclk2.mbtn2.height=30*DP1 aclk2.mbtn2.width=30*DP1 aclk2.mbtn2.left=355*DP1 aclk2.mbtn2.top=5*DP1
aclk2.mbtn2.caption="X"
aclk2.mbtn2.Font.Style="fsBold" aclk2.mbtn2.Font.Size=12*DP1
-----------------------
----------------------- aclk2.mpnl2 ----- 
aclk2.mpnl2=createPanel(mcmfrm)
aclk2.mpnl2.AutoSize=false
aclk2.mpnl2.height=280*DP1 aclk2.mpnl2.width=390*DP1 aclk2.mpnl2.left=5*DP1 aclk2.mpnl2.top=50*DP1
aclk2.mpnl2.caption=""
aclk2.mpnl2.Color=4694083
aclk2.mpnl2.Font.Style="fsBold" aclk2.mpnl2.Font.Size=0*DP1
-----------------------
----------------------- aclk2.slbl1 ----- 
aclk2.slbl1=createLabel(aclk2.mpnl2)
aclk2.slbl1.AutoSize=false
aclk2.slbl1.height=27*DP1 aclk2.slbl1.width=125*DP1 aclk2.slbl1.left=10*DP1 aclk2.slbl1.top=10*DP1
aclk2.slbl1.caption="Target Process:"
aclk2.slbl1.Font.Style="fsBold" aclk2.slbl1.Font.Size=14*DP1
aclk2.slbl1.OptimalFill=true
-----------------------
----------------------- aclk2.sedt1 ----- 
aclk2.sedt1=createEdit(aclk2.mpnl2)
aclk2.sedt1.AutoSize=false
aclk2.sedt1.height=25*DP1 aclk2.sedt1.width=170*DP1 aclk2.sedt1.left=140*DP1 aclk2.sedt1.top=10*DP1
aclk2.sedt1.text=""
aclk2.sedt1.alignment="taLeftJustify"
aclk2.sedt1.Font.Style="fsBold" aclk2.sedt1.Font.Size=0*DP1
-----------------------
----------------------- aclk2.sbtn1 ----- 
aclk2.sbtn1=createButton(aclk2.mpnl2)
aclk2.sbtn1.AutoSize=false
aclk2.sbtn1.height=25*DP1 aclk2.sbtn1.width=65*DP1 aclk2.sbtn1.left=315*DP1 aclk2.sbtn1.top=10*DP1
aclk2.sbtn1.caption="Scan"
aclk2.sbtn1.Font.Style="fsBold" aclk2.sbtn1.Font.Size=0*DP1
-----------------------
----------------------- aclk2.smemo1 ----- 
aclk2.smemo1=createMemo(aclk2.mpnl2)
aclk2.smemo1.AutoSize=false
aclk2.smemo1.height=225*DP1 aclk2.smemo1.width=200*DP1 aclk2.smemo1.left=10*DP1 aclk2.smemo1.top=45*DP1
aclk2.smemo1.Color=15321797
aclk2.smemo1.Font.Style="fsBold" aclk2.smemo1.Font.Size=0*DP1
aclk2.smemo1.ScrollBars="ssAutoBoth" aclk2.smemo1.wordWrap=true
-----------------------
----------------------- aclk2.sgbox1 ----- 
aclk2.sgbox1=createGroupBox(aclk2.mpnl2)
aclk2.sgbox1.AutoSize=false
aclk2.sgbox1.height=190*DP1 aclk2.sgbox1.width=160*DP1 aclk2.sgbox1.left=220*DP1 aclk2.sgbox1.top=45*DP1
aclk2.sgbox1.caption="SETTINGS"
aclk2.sgbox1.Font.Style="fsBold" aclk2.sgbox1.Color=15321797
-----------------------
----------------------- aclk2.slbl2 ----- 
aclk2.slbl2=createLabel(aclk2.sgbox1)
aclk2.slbl2.AutoSize=false
aclk2.slbl2.height=20*DP1 aclk2.slbl2.width=150*DP1 aclk2.slbl2.left=5*DP1 aclk2.slbl2.top=0*DP1
aclk2.slbl2.caption="Last Coord:"
aclk2.slbl2.alignment="taCenter"
aclk2.slbl2.Font.Style="fsBold" aclk2.slbl2.Font.Size=-14*DP1
-----------------------
----------------------- aclk2.slbl3 ----- 
aclk2.slbl3=createLabel(aclk2.sgbox1)
aclk2.slbl3.AutoSize=false
aclk2.slbl3.height=20*DP1 aclk2.slbl3.width=70*DP1 aclk2.slbl3.left=5*DP1 aclk2.slbl3.top=25*DP1
aclk2.slbl3.caption="X:"
aclk2.slbl3.alignment="taLeftJustify"
aclk2.slbl3.Font.Style="fsBold" aclk2.slbl3.Font.Size=-14*DP1
-----------------------
----------------------- aclk2.slbl4 ----- 
aclk2.slbl4=createLabel(aclk2.sgbox1)
aclk2.slbl4.AutoSize=false
aclk2.slbl4.height=20*DP1 aclk2.slbl4.width=70*DP1 aclk2.slbl4.left=80*DP1 aclk2.slbl4.top=25*DP1
aclk2.slbl4.caption="Y:"
aclk2.slbl4.alignment="taLeftJustify"
aclk2.slbl4.Font.Style="fsBold" aclk2.slbl4.Font.Size=-14*DP1
-----------------------
----------------------- aclk2.sbtn2 ----- 
aclk2.sbtn2=createButton(aclk2.sgbox1)
aclk2.sbtn2.AutoSize=false
aclk2.sbtn2.height=25*DP1 aclk2.sbtn2.width=110*DP1 aclk2.sbtn2.left=25*DP1 aclk2.sbtn2.top=55*DP1
aclk2.sbtn2.caption="Clear List"
-----------------------
----------------------- aclk2.sbtn3 ----- 
aclk2.sbtn3=createButton(aclk2.sgbox1)
aclk2.sbtn3.AutoSize=false
aclk2.sbtn3.height=22*DP1 aclk2.sbtn3.width=110*DP1 aclk2.sbtn3.left=25*DP1 aclk2.sbtn3.top=90*DP1
aclk2.sbtn3.Caption="Clear Line"
-----------------------
----------------------- aclk2.slbl6 ----- 
aclk2.slbl6=createLabel(aclk2.sgbox1)
aclk2.slbl6.AutoSize=false
aclk2.slbl6.height=25*DP1 aclk2.slbl6.width=150*DP1 aclk2.slbl6.left=5*DP1 aclk2.slbl6.top=115*DP1
aclk2.slbl6.caption="Loop Count:"
aclk2.slbl6.alignment="taCenter"
aclk2.slbl6.Font.Style="fsBold" aclk2.slbl6.Font.Size=-18*DP1
-----------------------
----------------------- aclk2.sedt3 ----- 
aclk2.sedt3=createEdit(aclk2.sgbox1)
aclk2.sedt3.AutoSize=false
aclk2.sedt3.height=22*DP1 aclk2.sedt3.width=70*DP1 aclk2.sedt3.left=45*DP1 aclk2.sedt3.top=142*DP1
aclk2.sedt3.text="1"
aclk2.sedt3.alignment="taCenter"
aclk2.sedt3.Font.Style="fsBold" aclk2.sedt3.Font.Size=0*DP1
-----------------------
----------------------- CheckBox Colors -----
function setCheckBoxColors(chk,clr,fclr)
   executeCodeLocalEx('uxtheme.SetWindowTheme', chk.handle, "", "")
   chk.Color = clr
   chk.Font.Color = fclr
end
load("return registerLuaFunctionHighlight('setCheckBoxColors')")()
-------------------------
----------------------- aclk2.slockbx ----- 
aclk2.slockbx=createCheckBox(aclk2.mpnl2)
aclk2.slockbx.AutoSize=false
aclk2.slockbx.height=25*DP1 aclk2.slockbx.width=150*DP1 aclk2.slockbx.left=225*DP1 aclk2.slockbx.top=240*DP1
aclk2.slockbx.caption=" LOCK SYSTEM"
aclk2.slockbx.Font.Style="fsBold" aclk2.slockbx.Font.Size=12*DP1
setCheckBoxColors(aclk2.slockbx,15392384,536870912)
-----------------------

aclk2.mbtn1.OnClick=function() mcmfrm.WindowState="wsMinimized" end

aclk2.mbtn2.OnClick=function()
  mcmfrm.close()
  --closeCE()
  return caFree
end

--############################################################################--
--############################################################################--

local cachedHwnd = 0
local recordCounter = 0
local currentLine = 0
local execCounter = 0
local readLine = true
local x1, y1, unitDelay1 = 0, 0, 0
local memoObject = aclk2.smemo1
local endCounter = 1

-- Cleanup old timers
if koordTmr then koordTmr.Destroy() koordTmr = nil end
if executionTmr then executionTmr.Destroy() executionTmr = nil end

-- Execution Heartbeat (Plays the recorded sequence)
executionTmr = createTimer()
executionTmr.interval = 100
executionTmr.Enabled = false

-- Recording Heartbeat (Visual Monitor & Counter)
koordTmr = createTimer()
koordTmr.interval = 100
koordTmr.Enabled = false
koordTmr.OnTimer = function()
    local x, y = getMousePos()
    aclk2.slbl3.caption = "X: " .. tostring(x)
    aclk2.slbl4.caption = "Y: " .. tostring(y)
    recordCounter = recordCounter + 1
end

--------------------------------------------------------------------------------
-- ENGINE LOGIC
--------------------------------------------------------------------------------

-- Capture current state and add to list
function koordSave()
    koordTmr.Enabled = true
    if not cachedHwnd or cachedHwnd == 0 then
        showMessage("ERROR: Use [Scan] button first!")
        return
    end

    local x, y = getMousePos()
    local delay = recordCounter
    local line = string.format("%d@%d@%d", x, y, delay)
    memoObject.Lines.Add(line)
    recordCounter = 0
    print("Recorded: " .. line)
end

-- Process the Memo list line-by-line
local function ClickEngine()
    local lines = memoObject.Lines
    if lines.Count == 0 then
        executionTmr.Enabled = false
        return
    end

    if readLine then
        if currentLine >= lines.Count then
            if endCounter <= 1 then
                executionTmr.Enabled = false
                showMessage("All loops completed successfully.")
                return
            else
                -- Loop back to start
                endCounter = endCounter - 1
                aclk2.slbl6.caption = "Loop Count ["..endCounter.."]"
                currentLine = 0
                execCounter = 0
                --print("Starting next loop...")
            end
        end

        local rawText = lines[currentLine]
        local rx, ry, rd = rawText:match("(%d+)@(%d+)@(%d+)")

        if rx then
            x1, y1, unitDelay1 = tonumber(rx), tonumber(ry), tonumber(rd)
            if unitDelay1 == 0 then unitDelay1 = 10 end -- Default to 1s if delay is 0
            readLine = false
        else
            currentLine = currentLine + 1
            return
        end
    end

    -- Wait for the required units before clicking
    if execCounter >= unitDelay1 then
        SilentClick(cachedHwnd, x1, y1)
        currentLine = currentLine + 1
        execCounter = 0
        readLine = true
    else
        execCounter = execCounter + 1
    end
end

executionTmr.OnTimer = ClickEngine

-- Start/Stop Control (F8 Toggle)
function startClk()
    sleep(200)
    if not executionTmr.Enabled then
        if cachedHwnd == 0 then
            showMessage("ERROR: Target window lost or not captured!\nPress [Scan] to re-capture.")
            return
        end
        currentLine = 0
        execCounter = 0
        endCounter = tonumber(aclk2.sedt3.text) or 1
        executionTmr.Enabled = true
        print("Auto-Clicker Status: RUNNING")
    else
        executionTmr.Enabled = false
        print("Auto-Clicker Status: STOPPED")
    end
end

--------------------------------------------------------------------------------
-- GUI INTERACTIONS & HOTKEYS
--------------------------------------------------------------------------------

function coordTick()
    sleep(200)
    koordSave()
end

-- Scan Button Logic
aclk2.sbtn1.OnClick = function()
    sleep(200)
    local targetExe = aclk2.sedt1.text

    if targetExe == "" then
        showMessage("Please enter the process name.\nExample: chrome.exe")
        if koordKey then koordKey.destroy(); koordKey = nil end
        return
    end

    cachedHwnd = getHandleSmart(targetExe)

    if cachedHwnd ~= 0 then
        if koordKey then koordKey.destroy() end
        koordKey = createHotkey(coordTick, VK_F10)
        print(string.format("Target Locked: %X. F10 (Capture) is active.", cachedHwnd))
        showMessage("Target Locked!\nF10 is now ready for recording.")
    else
        showMessage("Target process '" .. targetExe .. "' not found!\nEnsure the window is visible and not minimized.")
        if koordKey then koordKey.destroy(); koordKey = nil end
    end
end

-- Clear List Button (sbtn2)
aclk2.sbtn2.OnClick = function()
    aclk2.smemo1.Clear()
    currentLine = 0
    execCounter = 0
    endCounter = 1
    print("List cleared.")
end

-- Undo (Clear Last Line) Button (sbtn3)
aclk2.sbtn3.OnClick = function()
    local lines = aclk2.smemo1.Lines
    if lines.Count > 0 then
        lines.Delete(lines.Count - 1)
        if lines.Count == 0 then
            currentLine = 0
            execCounter = 0
            endCounter = 1
        end
        print("Last entry removed.")
    end
end

-- LOCK SYSTEM Logic (Logic/UI Switch)
aclk2.slockbx.OnChange = function(sender)
    local locked = sender.Checked

    -- Disable settings while locked
    aclk2.sgbox1.Enabled = not locked
    aclk2.sbtn1.Enabled = not locked
    aclk2.sedt1.Enabled = not locked

    if locked then
        -- EXECUTION MODE (F8 Active, F10 Disabled)
        if koordKey then koordKey.destroy(); koordKey = nil end
        if clkKey then clkKey.Destroy(); clkKey = nil end
        koordTmr.Enabled = false
        clkKey = createHotkey(startClk, VK_F8)

        endCounter = tonumber(aclk2.sedt3.text) or 0
        aclk2.slbl6.caption = "Loop Count ["..endCounter.."]"
        print("SYSTEM LOCKED: F8 is now active.")
    else
        -- SETUP MODE (F8 Disabled, F10 Ready)
        if clkKey then clkKey.destroy(); clkKey = nil end
        executionTmr.Enabled = false

        if cachedHwnd and cachedHwnd ~= 0 then
            koordKey = createHotkey(coordTick, VK_F10)
            koordTmr.Enabled = true
        end
        print("SYSTEM UNLOCKED: Editing enabled.")
    end
end


On browser pages, only use the tab focused with "Scan".
Note that background identifiers, such as page translations, can interfere with the module's task.
Sometimes, what is visible may not match what the module reads, which can confuse the module.

Until we meet again in another crazy article, enjoy!

[Lua], [Release], [Automation], [Background Clicker]

_________________
Hi Hitler Different Trainer forms for you!
https://forum.cheatengine.org/viewtopic.php?t=619279
Enthusiastic people: Always one step ahead
Do not underestimate me Master: You were a beginner in the past
Back to top
View user's profile Send private message Visit poster's website MSN Messenger
Display posts from previous:   
Post new topic   Reply to topic    Cheat Engine Forum Index -> Cheat Engine Tutorials -> LUA Tutorials All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You cannot download files in this forum


Powered by phpBB © 2001, 2005 phpBB Group

CE Wiki   IRC (#CEF)   Twitter
Third party websites