DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

Post gaming related scripts
lugia19
Posts: 9
Joined: 02 Mar 2020, 18:53

DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

25 Mar 2020, 18:34

I made a macrorecorder that's based on evilC's mousedelta library and that uses his LLmouse library for the playback.
I've only tested it with titanfall 2 myself. It uses inputhook for getting the various inputs.

The reason I made it was because all other macro recorders that I could find don't play nice with games as far as mouse movement goes, and evilC's LLmouse library works perfectly for that.

It includes the DeltaMouse and LLmouse libraries out of the box, so you don't need to download them. Credit to him for these:
https://www.autohotkey.com/boards/viewtopic.php?f=19&t=10159
https://www.autohotkey.com/boards/viewtopic.php?f=19&t=26137

The macro recorder itself is based on his mousedelta example.

IMPORTANT: If you want to record the starting mouse position, you'll need to set "RecordInitialMousePos" to True. It's disabled by default as enabling it causes issues with games, which is the main purpose of this script.

You simply press F9 to start/stop the recording, which also saves it.
It'll be saved as currentscript_ some number, it avoids overwriting any existing ones.

Finishing the recording also starts the latest saved script, which can be played back with F4.
Before you can record a new one you must quit out of the previous one with ALT-F12.
The scripts can also obviously be run by themselves, and the hotkeys are once again F4 and Alt-F12.

Thanks to @evilC for the libraries and @burque505 for the exit button code.
Thanks @need4speed for making me notice I forgot to record mouse clicks.

I'm sure my code looks disgusting since I'm not exactly an AHK veteran but hey it works.
One last, additional warning: the file size can get EXTREMELY large depending on how much mouse movement you're recording. It's not space efficient at all.

Code: Select all

#SingleInstance,Force
#NoEnv
 
Gui, Add, ListBox, w300 h200 hwndhOutput
Gui, Add, Text, xm w300 center, Hit F9 to toggle on / off
Gui, Show,, Game Macro Recorder/Writer

RecordInitialMousePos := False				;ENABLING THIS OPTION CAUSES ISSUES WITH GAMES. Feel free to enable it if recording a script that's NOT for a game. 
LineArray := []
HighAccuracy := True
MacroOn := 0
md := new MouseDelta("MouseEvent")
ih := InputHook("V")
ih.KeyOpt("{All}","N")
ih.KeyOpt("{F9}","E")
ih.NotifyNonText := True
ih.OnKeyUp := Func("IHKU")
ih.OnKeyDown := Func("IHKD")
filenumber := 0
LastTime := 0
StateArray := Object()
Q := DllCall("QueryPerformanceFrequency", "int64*", F)


return
 

GuiClose:
	md.Delete()
	md := ""
	ExitApp

F9::
	QPC(1)
	MacroOn := !MacroOn
	md.SetState(MacroOn)
	if (MacroOn == True)
	{
		text := "RECORDING START"
		GuiControl, , % hOutput, % text
		sendmessage, 0x115, 7, 0,, % "ahk_id " hOutput
		filename := A_ScriptDir "\currentscript_" filenumber ".ahk"
		while FileExist(filename)
			{
				filenumber := filenumber+1
				filename := A_ScriptDir "\currentscript_" filenumber ".ahk"
			}
		file := FileOpen(filename,"w")
		LineArray.Push("#SingleInstance,Force")
		LineArray.Push("SetWorkingDir %A_ScriptDir%")
		LineArray.Push("SetTitleMatchMode 2")
		LineArray.Push("#WinActivateForce")
		LineArray.Push("SetControlDelay 1")
		LineArray.Push("SetWinDelay 0")
		LineArray.Push("SetKeyDelay -1")
		LineArray.Push("SetMouseDelay -1")
		LineArray.Push("SetBatchLines -1")
		LineArray.Push("")
		llmouselib =
		(
			;---------------------------------------------------------------------------
SendMouse_LeftClick() { ; send fast left mouse clicks
;---------------------------------------------------------------------------
    DllCall("mouse_event", "UInt", 0x02) ; left button down
    DllCall("mouse_event", "UInt", 0x04) ; left button up
}


;---------------------------------------------------------------------------
SendMouse_RightClick() { ; send fast right mouse clicks
;---------------------------------------------------------------------------
    DllCall("mouse_event", "UInt", 0x08) ; right button down
    DllCall("mouse_event", "UInt", 0x10) ; right button up
}


;---------------------------------------------------------------------------
SendMouse_MiddleClick() { ; send fast middle mouse clicks
;---------------------------------------------------------------------------
    DllCall("mouse_event", "UInt", 0x20) ; middle button down
    DllCall("mouse_event", "UInt", 0x40) ; middle button up
}


;---------------------------------------------------------------------------
SendMouse_RelativeMove(x, y) { ; send fast relative mouse moves
;---------------------------------------------------------------------------
    DllCall("mouse_event", "UInt", 0x01, "UInt", x, "UInt", y) ; move
}


;---------------------------------------------------------------------------
SendMouse_AbsoluteMove(x, y) { ; send fast absolute mouse moves
;---------------------------------------------------------------------------
    ; Absolute coords go from 0..65535 so we have to change to pixel coords
    ;-----------------------------------------------------------------------
    static SysX, SysY
    If (SysX = "")
        SysX := 65535//A_ScreenWidth, SysY := 65535//A_ScreenHeight
    DllCall("mouse_event", "UInt", 0x8001, "UInt", x*SysX, "UInt", y*SysY)
}


;---------------------------------------------------------------------------
SendMouse_Wheel(w) { ; send mouse wheel movement, pos=forwards neg=backwards
;---------------------------------------------------------------------------
    DllCall("mouse_event", "UInt", 0x800, "UInt", 0, "UInt", 0, "UInt", w)
}

QPC_Sleep(S)
{
    global Q,F
    DllCall("QueryPerformanceCounter", "int64*", C1)
    while (((C2 - C1) / F) < S)
        DllCall("QueryPerformanceCounter", "int64*", C2)
    return true
}
QPC(R := 0)
{
	global Q,F
    static P := 0
    return !DllCall("QueryPerformanceCounter", "int64*", Q) + (R ? (P := Q) / F : (Q - P) / F) 
}
		)
		
		

		LineArray.Push(llmouselib)		;Credit to evilC for the LLmouse library too
		LineArray.Push("")
		LineArray.Push("!F12::")
		LineArray.Push("ExitApp")
		LineArray.Push("return")
		LineArray.Push("")
		LineArray.Push("F4::")
		LineArray.Push("Q := DllCall(""QueryPerformanceFrequency"", ""int64*"", F)")
		LineArray.Push("QPC(1)")
		if (RecordInitialMousePos == True )
		{
			MouseGetPos,xpos,ypos
			LineArray.Push("MouseMove, " xpos ", " ypos)
		}
		LastTime := QPC(0)
		ih.Start()
	}
	if (MacroOn == False)
	{
		LineArray.Push("return")
		
		;WRITE ALL TO FILE HERE
		for index, element in LineArray 
		{
			file.writeline(element)
		}
		
		LineArray := []
		
		Loop, 7
			GuiControl, , % hOutput, `n
		sendmessage, 0x115, 7, 0,, % "ahk_id " hOutput
		text := "Waiting for script to exit (F4 to play it back, ALT-F12 to quit)"
		GuiControl, , % hOutput, % text
		Loop, 7
			GuiControl, , % hOutput, `n
		sendmessage, 0x115, 7, 0,, % "ahk_id " hOutput
		filename := A_ScriptDir "\currentscript_" filenumber ".ahk"
		ih.Stop()
		file.close()
		runwait %filename%
		Loop, 15
			GuiControl, , % hOutput, `n
		sendmessage, 0x115, 7, 0,, % "ahk_id " hOutput
	}
	return

~LButton::
	
	if (MacroOn == False)
		return
	t := QPC(0)
	LineArray.Push("QPC_Sleep(" (t-LastTime) ")")
	LineArray.Push("Send,{LButton Down}")
	;GuiControl, , % hOutput, % text
	;sendmessage, 0x115, 7, 0,, % "ahk_id " hOutput
	LastTime := t
return

~LButton Up::
	
	if (MacroOn == False)
		return
	t := QPC(0)
	LineArray.Push("QPC_Sleep(" (t-LastTime) ")")

	LineArray.Push("Send,{LButton Up}")
	;GuiControl, , % hOutput, % text
	;sendmessage, 0x115, 7, 0,, % "ahk_id " hOutput
	LastTime := t
return

~RButton::
	
	if (MacroOn == False)
		return
	t := QPC(0)
	LineArray.Push("QPC_Sleep(" (t-LastTime) ")")

	LineArray.Push("Send,{RButton Down}")
	;GuiControl, , % hOutput, % text
	;sendmessage, 0x115, 7, 0,, % "ahk_id " hOutput
	LastTime := t
return

~RButton Up::
	
	if (MacroOn == False)
		return
	t := QPC(0)
	LineArray.Push("QPC_Sleep(" (t-LastTime) ")")
		
	LineArray.Push("Send,{RButton Up}")
	;GuiControl, , % hOutput, % text
	;sendmessage, 0x115, 7, 0,, % "ahk_id " hOutput
	LastTime := t
return

~MButton::
	
	if (MacroOn == False)
		return
	t := QPC(0)
	LineArray.Push("QPC_Sleep(" (t-LastTime) ")")
	LineArray.Push("Send,{MButton Down}")
	;GuiControl, , % hOutput, % text
	;sendmessage, 0x115, 7, 0,, % "ahk_id " hOutput
	LastTime := t
return

~MButton Up::
	if (MacroOn == False)
		return
	t := QPC(0)
	LineArray.Push("QPC_Sleep(" (t-LastTime) ")")

	LineArray.Push("Send,{MButton Up}")
	;GuiControl, , % hOutput, % text
	;sendmessage, 0x115, 7, 0,, % "ahk_id " hOutput
	LastTime := t
return

IHKD(ih,VK,SC)
{
	global hOutput
	;static text := ""
	global LineArray
	global LastTime
	global StateArray
	
	
	t := QPC(0)   
	
	if (StateArray[VK] == 0)
	{
		return
	}
		
	StateArray[VK] := 0
	;text := "Delta time " (t - LastTime)*1000 " ms : Send,{" GetKeyName(Format("vk{:X}sc{:X}", VK, SC)) " Down}"
	LineArray.Push("QPC_Sleep(" (t-LastTime) ")")

	LineArray.Push("Send,{" Format("vk{:02X}",VK) " Down}")
	;GuiControl, , % hOutput, % text
	;sendmessage, 0x115, 7, 0,, % "ahk_id " hOutputt
	LastTime := t
}

IHKU(ih,VK,SC)
{
	global hOutput
	static text := ""
	global LineArray
	global LastTime
	global StateArray
	
	
	t := QPC(0)  	
	if (VK == 120)
		return
	
	StateArray[VK] := 1
	
	;text := "Delta time " (t - LastTime)*1000 " ms : Send,{vk" VK " Up}"
	LineArray.Push("QPC_Sleep(" (t-LastTime) ")")

	LineArray.Push("Send,{" Format("vk{:02X}",VK) " Up}")
	;GuiControl, , % hOutput, % text
	;sendmessage, 0x115, 7, 0,, % "ahk_id " hOutput
	LastTime := t
}

; Gets called when mouse moves
; x and y are DELTA moves (Amount moved since last message), NOT coordinates.
MouseEvent(MouseID, x := 0, y := 0){
	global LineArray
	;global hOutput
	
	;static text := ""
	global LastTime
	
	
	;if (x==0 && y == 0)
	;	return	;Click input
	
	t := QPC(0)
	LineArray.Push("QPC_Sleep(" (t-LastTime) ")")

	LineArray.Push("SendMouse_RelativeMove(" x "," y ")")
	;text := "x: " x ", y: " y (LastTime ? (", Delta Time: " (t - LastTime)*1000 " ms, MouseID: " MouseID) : "")
	;GuiControl, , % hOutput, % text
	;sendmessage, 0x115, 7, 0,, % "ahk_id " hOutput
	LastTime := t
}



;QPC code - Credit to @SKAN and @jNizM
QPC_Sleep(S)
{
	global Q,F
    DllCall("QueryPerformanceCounter", "int64*", C1)
    while (((C2 - C1) / F) < S)
        DllCall("QueryPerformanceCounter", "int64*", C2)
    return true
}

QPC(R := 0)
{
	global Q,F
    static P := 0
    return !DllCall("QueryPerformanceCounter", "int64*", Q) + (R ? (P := Q) / F : (Q - P) / F) 
}



;MouseDelta library - Credit to @evilC

; Instantiate this class and pass it a func name or a Function Object
; The specified function will be called with the delta move for the X and Y axes
; Normally, there is no windows message "mouse stopped", so one is simulated.
; After 10ms of no mouse movement, the callback is called with 0 for X and Y
Class MouseDelta {
	State := 0
	__New(callback){
		;~ this.TimeoutFn := this.TimeoutFunc.Bind(this)
		this.MouseMovedFn := this.MouseMoved.Bind(this)

		this.Callback := callback
	}

	Start(){
		static DevSize := 8 + A_PtrSize, RIDEV_INPUTSINK := 0x00000100
		; Register mouse for WM_INPUT messages.
		VarSetCapacity(RAWINPUTDEVICE, DevSize)
		NumPut(1, RAWINPUTDEVICE, 0, "UShort")
		NumPut(2, RAWINPUTDEVICE, 2, "UShort")
		NumPut(RIDEV_INPUTSINK, RAWINPUTDEVICE, 4, "Uint")
		; WM_INPUT needs a hwnd to route to, so get the hwnd of the AHK Gui.
		; It doesn't matter if the GUI is showing, it still exists
		Gui +hwndhwnd
		NumPut(hwnd, RAWINPUTDEVICE, 8, "Uint")
 
		this.RAWINPUTDEVICE := RAWINPUTDEVICE
		DllCall("RegisterRawInputDevices", "Ptr", &RAWINPUTDEVICE, "UInt", 1, "UInt", DevSize )
		OnMessage(0x00FF, this.MouseMovedFn)
		this.State := 1
		return this	; allow chaining
	}
	
	Stop(){
		static RIDEV_REMOVE := 0x00000001
		static DevSize := 8 + A_PtrSize
		OnMessage(0x00FF, this.MouseMovedFn, 0)
		RAWINPUTDEVICE := this.RAWINPUTDEVICE
		NumPut(RIDEV_REMOVE, RAWINPUTDEVICE, 4, "Uint")
		DllCall("RegisterRawInputDevices", "Ptr", &RAWINPUTDEVICE, "UInt", 1, "UInt", DevSize )
		this.State := 0
		return this	; allow chaining
	}
	
	SetState(state){
		if (state && !this.State)
			this.Start()
		else if (!state && this.State)
			this.Stop()
		return this	; allow chaining
	}

	Delete(){
		this.Stop()
		;~ this.TimeoutFn := ""
		this.MouseMovedFn := ""
	}
	
	; Called when the mouse moved.
	; Messages tend to contain small (+/- 1) movements, and happen frequently (~20ms)
	MouseMoved(wParam, lParam){
		Critical
		; RawInput statics
		static DeviceSize := 2 * A_PtrSize, iSize := 0, sz := 0, pcbSize:=8+2*A_PtrSize, offsets := {x: (20+A_PtrSize*2), y: (24+A_PtrSize*2)}, uRawInput
 
		static axes := {x: 1, y: 2}
 
		; Get hDevice from RAWINPUTHEADER to identify which mouse this data came from
		VarSetCapacity(header, pcbSize, 0)
		If (!DllCall("GetRawInputData", "UPtr", lParam, "uint", 0x10000005, "UPtr", &header, "Uint*", pcbSize, "Uint", pcbSize) or ErrorLevel)
			Return 0
		ThisMouse := NumGet(header, 8, "UPtr")

		; Find size of rawinput data - only needs to be run the first time.
		if (!iSize){
			r := DllCall("GetRawInputData", "UInt", lParam, "UInt", 0x10000003, "Ptr", 0, "UInt*", iSize, "UInt", 8 + (A_PtrSize * 2))
			VarSetCapacity(uRawInput, iSize)
		}
		sz := iSize	; param gets overwritten with # of bytes output, so preserve iSize
		; Get RawInput data
		r := DllCall("GetRawInputData", "UInt", lParam, "UInt", 0x10000003, "Ptr", &uRawInput, "UInt*", sz, "UInt", 8 + (A_PtrSize * 2))
 
		x := 0, y := 0	; Ensure we always report a number for an axis. Needed?
		x := NumGet(&uRawInput, offsets.x, "Int")
		y := NumGet(&uRawInput, offsets.y, "Int")
 
		this.Callback.(ThisMouse, x, y)
 
		;~ ; There is no message for "Stopped", so simulate one
		;~ fn := this.TimeoutFn
		;~ SetTimer, % fn, -50
	}
 
	;~ TimeoutFunc(){
		;~ this.Callback.("", 0, 0)
	;~ }
 
}
Last edited by lugia19 on 19 Jul 2020, 17:52, edited 8 times in total.
need4speed
Posts: 143
Joined: 22 Apr 2016, 06:50

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

26 Mar 2020, 05:29

thanks for your code, there can't be enough macrorecorder. I noticed the generated script will not run, as some functions are missing. maybe you want to add a panic button. cheers
Spoiler
burque505
Posts: 1731
Joined: 22 Jan 2017, 19:37

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

26 Mar 2020, 09:35

@lugia19 , @need4speed , thanks you very much for this, and thanks to @evilC for his libs.
In the code below @need4speed 's suggested panic button is Alt-F12, and his functions are included as

Code: Select all

#Include need4speed.ahk
Spoiler
Regards,
burque505
need4speed
Posts: 143
Joined: 22 Apr 2016, 06:50

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

26 Mar 2020, 10:14

thanks @burque505 for your contribution in adding the panic button (the code posted above was made by Wolf_II not me).
I noticed in some cases the generated scripts will not run, because the last line is sometimes truncated.
burque505
Posts: 1731
Joined: 22 Jan 2017, 19:37

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

26 Mar 2020, 10:36

@need4speed , I'm seeing the same thing about truncation. Trying to find a way to fix it, please post if you get there first :D
Regards,
burque505
need4speed
Posts: 143
Joined: 22 Apr 2016, 06:50

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

26 Mar 2020, 10:48

@burque505 I really appreciate your upcoming improvements. plz don't wait for me. :lol:
edit: I noticed MouseClick is not recorded or am I missing something?
User avatar
Hajin
Posts: 51
Joined: 13 May 2016, 09:16

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

26 Mar 2020, 15:20

I like macro recorders, it is a quick way to make a keybot even knowing how to make one manually, I was interested in this one because of the apparent speed of use but the generated script does not work and presents this error:
aa.png
aa.png (47.47 KiB) Viewed 7749 times
The first error of this type was solved by updating the autohotkey but in this last one i think i am not currently advanced in autohotkey enough to fix this.
I returned download this other macro recorder also made with autohotkey:
https://www.macrocreator.com/
burque505
Posts: 1731
Joined: 22 Jan 2017, 19:37

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

26 Mar 2020, 15:43

@Hajin , you might try this. Everything needs to go in the same directory.
Along with the code below, you also need MouseDelta.ahk and LLMouse.ahk, which you can get from the links above.
Alt-F12 to stop the script when it runs. Other directions are as above.

Modified DeltaMacros.ahk:
Spoiler
need4speed.ahk:
Spoiler
Regards,
burque505
User avatar
Hajin
Posts: 51
Joined: 13 May 2016, 09:16

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

26 Mar 2020, 16:08

bb.png
bb.png (53.16 KiB) Viewed 7728 times
Maybe it's something on my computer, I give up.
burque505
Posts: 1731
Joined: 22 Jan 2017, 19:37

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

26 Mar 2020, 17:28

I hate for it not to work for you. It looks like all the files are in the right place to me.
EDIT: Sorry, bad pasting job. Still trying some new stuff.
lugia19
Posts: 9
Joined: 02 Mar 2020, 18:53

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

27 Mar 2020, 15:57

sorry for not posting here, I thought I'd get email notifications but apparently not.

I'll add a panic button soon, haven't had the time to work on it. As for your error @Hajin it might be case sensitive? Try putting the names in all lowercase, both for llmouse.ahk and mousedelta.ahk
I'll also add an option to run the latest generated script without needing to close DeltaMacros, using an additional key.

I'll also just include the functions directly instead of referencing the ahk file since that's easier, I wanted to keep them separate as to avoid redistributing evilC's code.

As far as the truncation goes, pretty sure it's because I'm only closing the filestream when exiting the GUI (or at least I assume that's the case). I'll move to closing it when the recording stops, and starting it again makes a new script.
burque505
Posts: 1731
Joined: 22 Jan 2017, 19:37

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

27 Mar 2020, 16:01

@lugia19 , thanks again for your code!
Here's what I did for a panic button, if it helps.
Spoiler
Regards,
burque505
lugia19
Posts: 9
Joined: 02 Mar 2020, 18:53

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

27 Mar 2020, 16:30

@need4speed I completely forgot to record mouseclick. Will fix it now.
User avatar
Hajin
Posts: 51
Joined: 13 May 2016, 09:16

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

28 Mar 2020, 06:54

@lugia19
Apparently you updated the script and now it is working here...
The execution of the generated script is so accurate in the movement of the mouse :dance:
EDITED:
I would prefer the script to record the initial position of the cursor to put the cursor in such position at the beginning, but I think I can place this manually in the generated script.
Last edited by Hajin on 11 Jun 2023, 23:46, edited 2 times in total.
lugia19
Posts: 9
Joined: 02 Mar 2020, 18:53

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

28 Mar 2020, 08:05

Hajin wrote:
28 Mar 2020, 06:54
@lugia19
Apparently you updated the script and now it is working here...
The execution of the generated script is so accurate in the movement of the mouse :dance:
EDITED:
I would prefer the script to record the initial position of the cursor to put the cursor in such position at the beginning, but I think I can place this manually in the generated script.
The reason it doesn't is because it's made for games, where moving the mouse to an absolute position would cause to break it.
I'll add it as a toggle that's off by default.
lugia19
Posts: 9
Joined: 02 Mar 2020, 18:53

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

28 Mar 2020, 08:14

@Hajin added a toggle for it.
burque505
Posts: 1731
Joined: 22 Jan 2017, 19:37

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

28 Mar 2020, 08:55

:bravo: @lugia19, thank you! While you were coding that, apparently, I was coding this, which is inferior as it has no toggle. It just sets the mouse position to the initial position.
Spoiler
Regards, burque505
User avatar
Hajin
Posts: 51
Joined: 13 May 2016, 09:16

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

28 Mar 2020, 10:56

burque505 wrote:
28 Mar 2020, 08:55
:bravo: @lugia19, thank you! While you were coding that, apparently, I was coding this, which is inferior as it has no toggle. It just sets the mouse position to the initial position.
Spoiler
Regards, burque505
This version of the script is also very good and accurate.
burque505
Posts: 1731
Joined: 22 Jan 2017, 19:37

Re: DeltaMacros - Macro Recoder based on LLmouse/Mousedelta

28 Mar 2020, 11:04

@Hajin , thank you. Glad it works.
Regards,
burque505

Return to “Gaming Scripts (v1)”

Who is online

Users browsing this forum: No registered users and 37 guests