Skip to content

Accurately Controlling Your Mouse Pointer

L5474 edited this page Mar 24, 2020 · 1 revision

The built-in mouse plugin cannot accurately control the mouse, since it can only control the the "delta", or speed in a specific direction, and Python's execution timing isn't always 100% accurate. Consequently, it also cannot move it to a specific position.

However, this guide will show you how to do just that using the ctypes Python library.

ctypes comes with FreePIE, so you don't need to install it separately. It is a library that allows Python to access DLLs and shared libraries, and provides C-compatible data types. You can read more about it on the official Python documentation.

One of the shared libraries accessible from the get-go is User32.dll. It contains quite a few methods related to UI and UX, one of which allows you to control and read the position of your mouse cursor - SetCursorPos() and GetCursorPos() respectively.

SetCursorPos takes in two values, the target x and y, which correspond to each pixel of your monitor. Make sure the values are plain integers, otherwise the position of the cursor will be massively inaccurate.

All you have to do is import ctypes. Here is an example script that is meant to control your cell in the popular web game agar.io using a Wiimote and Nunchuck:

import ctypes

# sign - Returns -1 for negatives, 0 for zero and 1 for positives
# val			input value
def sign(val):
	return (1 * (val > 0)) + (-1 * (val < 0))

# circularArea - maps x and y values into a circular area.
# Mimics how a joystick would work, where the position will return to the center.
# pixelRadius	radius of the area in pixels
# origX			x value of the origin or center of area
# origY 		y value of origin of the area
# inX			input x value
# inY			input y value
def circularArea(pixelRadius, origX, origY, inX, inY):
	scaledX = filters.mapRange(-inX, jsRange, -jsRange, -pixelRadius, pixelRadius)
	scaledY = filters.mapRange(-inY, jsRange, -jsRange, -pixelRadius, pixelRadius)
	posX = origX + scaledX
	posY = origY + scaledY
	return posX, posY

# dz - establishes a deadzone (probably not necessary)
# val 			input value
# dz			deadzone, everything under it returns a 0
def dz(val, dz):
	if abs(val) > dz:
		return val
	else:
		return 0	
	
def update():
	# circular range
	posX, posY = circularArea(200, scrCenterX, scrCenterY, dz(nchk.stick.x, 2), -dz(nchk.stick.y, 2))
	posX = int(posX)
	posY = int(posY)
	
	# actual bindings
	#	only process joystick input when A is pressed down, for safety reasons.
	if wiimote[0].buttons.button_down(WiimoteButtons.A):
		ctypes.windll.user32.SetCursorPos(posX, posY)
	#	release mass
	keyboard.setKey(Key.W, nchk.buttons.button_down(NunchuckButtons.C))
	#	split
	keyboard.setKey(Key.Space, nchk.buttons.button_down(NunchuckButtons.Z))
	
	# watching values
	diagnostics.watch(nchk.stick.x)
	diagnostics.watch(nchk.stick.y)
	diagnostics.watch(posX)
	diagnostics.watch(posY)

if starting:
	# joystick variables
	jsRange = 100
	
	# screen variables
	scrWidth = 1600
	scrHeight = 900
	scrCenterX = scrWidth / 2
	scrCenterY = scrHeight / 2
	
	# aliases
	nchk = wiimote[0].nunchuck
	
	nchk.update += update 

As shown in the script, make sure you never let the program control your cursor 100% of the time, just to be safe.


HUGE credit to HarvesteR on mtbs3d.com, more specifically this thread (accessed through Web Archive because the site is still down)