Skip to main content

Aimbot Smoothing Using a Proportional Control

In conventional aimbot implementation for legitbots, you’re working off the basis of a constant smoothing rate, causing the aimbot to sack behind moving targets or otherwise look super unnatural if the smoothing ends up being too low or even osciliate around the target.

In order to counteract this, we will implement a feedback control system in which we apply a correction to our controlled variable. This correction is proportional to the difference between our desired value and the measured value.

Proportional controllers give an output to an actuator that is a multiple of the error; as in, they respond to the size of the error. The multiple is the gain. When the proportional band is too high, a large error is required to make the necessary change. When the proportional band is too low, a small error causes a large change in the output that overcompensates; this can send the system oscillating out of control.

I don’t speak engineering, translate this bullshit to terms I understand?

We want to scale our aimbot smoothing depending on the size of an error (Distance to the target between frames) over a timeframe. This allows us to calculate an expected velocity to reach the target. This value we can then clamp to an absolute maximum output value (Think of this as a minimum smoothing). The error will then be the difference between what we could have moved and what we actually moved.

Implementation

My personal implementation differentiates between the tracking of targets and selection of targets. For your purpose, your target tracking will most likely just be making sure you aimbot on the same target as long as they’re still valid.

Target selection can be based on whatever parameters you personally prefer, for legit aimbot, this is usually a simple “Targets closest to crosshair or whatever target I was focusing in the last iteration”.

If we don’t have a target, we “tick” the aimbot. I’ll get back to what this means.

If we have a target but the aimbot is not activated, we also tick the aimbot.

If we have a target and the aimbot is active, we determine the new mouse position and recalibrate the error for the next iteration.

Aimbot Tick

If we don’t have any targets or the aimbot is not currently active, we cannot use the previously gathered error. Thus we will reset these.

public void Tick(IMouseInjector injector, double dt, bool overrideAimbot)
{
    // If you were to implement additional aiming types, you would be able to handle some of the input in here.
    // The most common example is a combined legit-bot and flick-bot for games such as Overwatch
    // In that case you would specify that the aimbot should be overridden, even if you would otherwise determine it did not make sense to aimbot
    Clear();
}

public void Clear()
{
	_time = 0.0;
	_newErrorX = 0.0;
	_newErrorY = 0.0;
}

There can be a lot more depth to what an Aimbot Tick is, but for now, we’ll keep it simple and just reset the time and our error rate. Extra features that can be added here is left as an exercise for the reader.

Aimbot movement

Since we could have implementation logic inside of an Aimbot Tick, the target is set as a field on the class before we call Move.

Previously I mentioned we had control variables. In our case, this is our Gain and our Maxspeed as properties.

public double MaxSpeedX { get; set; }
public double MaxSpeedY { get; set; }
public double Gain { get; set; }

public ProportionalAimer()
{
    Gain = 0.3;
    MaxSpeedX = 700.0;
	MaxSpeedY = 300.0;
	
    Clear();
}

Furthermore, we define our needed calculated values for the proportial logic.

private double _newErrorX;
private double _newErrorY;
private Point _position;
private Point _target;
private double _time;

Now we can implement the remaining part of our aiming logic.

Please note that I am working in screen-coordinates and not in world-coordinates.

public void Aim(IMouseInjector injector, Point target, Point position, double dt)
{
	_target = target;
	_position = position;
	Move(injector, dt);
}

private void Move(IMouseInjector injector, double dt)
{
	_time += dt;

	if (_target != Point.Empty)
	{
		var errorx = _target.X - _position.X;
		var errory = _target.Y - _position.Y;

		// Determine speed using proportional control.
		var xVelocity = errorx * Gain * _time * 100.0 + _newErrorX;
		var yVelocity = errory * Gain * _time * 100.0 + _newErrorY;

		// Scale the speed over the time
		var xSpeed = xVelocity / dt;
		var ySpeed = yVelocity / dt;

		// Clamp values to maximum speed
		xSpeed = Math.Clamp(xSpeed, -MaxSpeedX, MaxSpeedX);
		ySpeed = Math.Clamp(ySpeed, -MaxSpeedY, MaxSpeedY);

		// Move using proportional control
		xVelocity = xSpeed * dt;
		yVelocity = ySpeed * dt;

		var targetPointX = (int) Math.Round(xVelocity);
		var targetPointY = (int) Math.Round(yVelocity);
		
		// Update the error to be the difference in unrounded values between movement and velocity
		_newErrorX = xVelocity - targetPointX;
		_newErrorY = yVelocity - targetPointY;

		if (targetPointX != 0 || targetPointY != 0)
		{
			injector.Move(targetPointX, targetPointY);
		}
	}
	else
	{
		_newErrorX = 0.0;
		_newErrorY = 0.0;
	}

	_time = 0.0;
}   

Lets walk through it.

We define the error as the difference between our correct position and the target position (Setpoint difference to process variable). This is technically the error we want to correct.

The output of a proportional controller is the multiplication product of the error signal and the proportional gain.

Furthermore, we can define limitation on P out<\sub> - Such as a maximum and a minimum. We clamp our speed values to be withing a maximum speed that we want.

We want to scale our values over time to get similar results at different framerates for our aimbot loop and scale this to the proportional constant. This gives us the target point we want to move our mouse to.

At the end, the difference between what we actually moved and what we wanted to move is equal to our error rate. This is then used in continunous iterations to scale our movement.

comments powered by Disqus