MyScienceBlog

DIY Aimbot

Luke
15.05.2023

Disclaimer: Der gezeigte Aimbot dient nur zu Bildungszwecken. Ich rate ausdrücklich davon ab, ihn in einem Spiel gegen andere Spieler einzusetzen. Bei Verwendung muss man mit einem Ausschluss aus dem Spiel rechnen (permanenter Ban).

In diesem Artikel zeige ich euch, wie ein Aimbot in einem Panzerspiel funktionieren könnte. Die Formel und die Idee für den Aimbot gab mir folgender Artikel in der Steam-Community: Perfect accuracy with CALCULATIONS

Der folgende Code ist in Python geschrieben und kann nacheinander in ein Script kopiert werden.

Für den Bot brauchen wir die folgenden Bibliotheken:
from PIL import ImageGrab
import cv2
import numpy as np
import win32gui
import time
import math

PIL und win32gui brauchen wir, um das Spielfeld zu bekommen. Dies geschieht durch einen einfachen Screenshot. Numpy und Math brauchen wir zur Berechnung.Time ist für einen kleinen Delay zuständig, da sonst Screenshot-Probleme auftreten können.

Als erstes müssen wir das Spielfeld unserem Bot zeigen. Hierfür schreiben wir die Funktion get_window():
def get_window():
    hwnd = win32gui.FindWindow(None, GAMENAME) #select Game
    win32gui.SetForegroundWindow(hwnd) #push it to foreground
    time.sleep(0.1) #wait for it to be in foreground

    rect = (0,50,1920,950) #image has to be in the upper left corner: 1920x1080
    image = np.array(ImageGrab.grab(rect)) #gescreenshot
    return image

GAMENAME muss mit dem Spielnamen ersetzt werden. Wenn man eine andere Auflösung als 1920x1080 hat, muss man die Pixelanzahl umrechnen. Weitere Angaben dazu im Steam-Artikel.

Nun müssen wir die gegnerischen Panzer und unseren Panzer mit der Funktion detector(image,color) ausfindig machen:
def detector(image,color):
    img = image
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

    if color == "red":
        # color range in hsv for red
        lower = np.array([160, 100, 100])
        upper = np.array([180, 255, 255])
    elif color == "green":
        # color range in hsv for green
        lower = np.array([50, 150, 150])
        upper = np.array([70, 255, 255])

    mask = cv2.inRange(hsv, lower, upper)
    #cv2.imshow("mask", mask)
    #cv2.waitKey(0)

    points = cv2.findNonZero(mask) #get points of the color
    res_points = points.reshape(len(points), -1) #reshape
    sorted_p = res_points[res_points[:, 0].argsort()] #sort by first column value

    #detect groups of red pixels to find enemies (works for single target too)
    enemies = [] #array for the groups
    while True:
        sorted_p_l = len(sorted_p) #get started length of points
        for i in range(sorted_p_l):
            if i < sorted_p_l-1: #for out of range error
                #proof if point as a meaningfull distance to next one
                current_point = sorted_p[i]
                next_point = sorted_p[i+1]
                difference = next_point[0] - current_point[0]
                if difference > 50: #proof if the difference is bigger than min distance
                    enemies.append([sorted_p[:i+1]]) #append this range
                    sorted_p = sorted_p[i+1:] #cut appended range of
                    break

        if sorted_p_l == len(sorted_p): #proof if method found (deleted) something
            enemies.append([sorted_p]) #append last group cause you cant find a distance cause there is only one left
            break

    enemies_pos = []
    for enemy in enemies:
        if len(enemy[0]) < 250: #if they have less than 300 points it isnt an enemy like the red arrow over your tank
            pass
        else:
            #get min and max xs and ys
            min = np.min(enemy[0], axis=0).flatten()
            max = np.max(enemy[0], axis=0).flatten()

            # filter to get middle pixel of tank
            median_max = int(np.median([min[0], max[0]]))  # convert to int to cut .5 of
            x = median_max
            y = min[1]+20 #+20 so i get the middle and not the top
            enemies_pos.append((x,y)) #append

    return enemies_pos

Dieser langer Codeblock macht Folgendes:
  1. Wir suchen uns alle Pixel der ausgewählten Farbe (Grün oder Rot)
  2. Nun gruppieren wir die Pixel zu Clustern und machen daran die Panzer ausfindig. Hierbei muss eine gewisse Anzahl an Pixeln erreicht sein.
  3. Jetzt flatten wir den Array und suchen uns das Minimum und Maximum. So können wir die Position des Clusters bestimmen und bekommen X- und Y-Werte jedes Gegners zurück.
Nachdem wir dann per Input in der Main-Methode, die wir am Ende begutachten, den Gegner ausgewählt haben, berechnen wir mithilfe der Formeln aus dem Steam-Artikel die Kraft mit der Methode calc_power(x,y,angle). Hiebei sind X und Y relativ:
def calc_power(x,y,angle):
    g = (-379.106)
    q = 0.0518718
    angle = math.radians(angle)

    velocity = math.sqrt(((-1*g)*(x**2))/(2*(math.cos(angle)**2)*(math.tan(angle)*x-y)))
    power = (-2/(g*q)) * velocity
    return power

Nun vereinen wir die einzelnen Methoden in der Main-Methode run_live():
def run_live():
    img = get_window()
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) #cv2 reads in BGR and we want RGB

    me = detector(img, "green")[0]
    enemies = detector(img, "red")

    #draw and save image
    drawed = cv2.circle(img, me, radius=5, color=(255, 0, 255), thickness=2)
    for enemy in enemies:
        drawed = cv2.circle(drawed, enemy, radius=5, color=(0, 255, 255), thickness=2)
    cv2.imwrite("drawed.png", drawed)

    print("Enemies detected: ",len(enemies))
    enemy = int(input("Enemy: "))-1 #so we start counting by 1 and not zero
    #get distance of enemy
    x = me[0] - enemies[enemy][0]
    y = me[1] - enemies[enemy][1]
    if x < 0:
        x = x * -1

    #insert angle
    angle = int(input("angle: "))
    print("Power: ",calc_power(x,y,angle))

Hier müssen wir wie erwähnt den Gegner per Zahl auswählen (es wird von links nach rechts gezählt) und den Winkel angeben in den wir schießen möchten, da z.B. Berge im Weg sein könnten und das Script diese nicht erkennen kann. Auch wird ein Bild namens drawed.png erstellt, auf dem man die Erkennung der Gegner und sich selbst nachvollziehen kann (Gegner: Gelber Kreis, Spieler: Lila Kreis):

Skizze zur Veranschaulichung

Zum Schluss muss man nur noch die Funktion aufrufen:
run_live()

Mögliche Probleme
In vielen Spielen gibt es noch andere Dinge, die die gleiche bzw. ähnliche Farbe wie die gegnerischen Panzer haben. Da der Bot nur eindimensional scannt, können dadurch Fehler entstehen. Auch wenn Panzer übereinander sind, entstehen Fehler.

Eine weitere Besonderheit ist, dass zwei Panzer sehr nah nebeneinander stehen, werden sie als ein Panzer erkannt. Dies ist nicht weiter schlimm, da man so in die Mitte der beiden Panzer trifft und optimalen Schaden an beiden Panzern ausrichtet.