Python colorbot that works with and without hardware
import configparser
import numpy as np
import dxcam
import os


configFile = configparser.ConfigParser()
runtime = 0
recoil_offset = 0

# Network
ip = None
port = None
client = None
com_type = None
com_port = None
board = None

# Aim
offset = None
smooth = None
speed = None
xMultiplier = None
recoil_mode = None
max_offset = None
recoilX = None
recoilY = None
recoil_recover = None

# Screen
cam = None
center = None
fov = None
region = None
resolution = None
upper_color = None
lower_color = None
fps = None
region_left = None
region_top = None
region_right = None
region_bottom = None

# Booleans
toggleAim = False
toggleRecoil = False


def read_config():
    global configFile
    global ip
    global port
    global client
    global com_type
    global com_port
    global offset
    global smooth
    global speed
    global xMultiplier
    global recoil_mode
    global max_offset
    global recoilX
    global recoilY
    global recoil_recover
    global cam
    global center
    global fov
    global region
    global resolution
    global upper_color
    global lower_color
    global fps
    global region_left
    global region_top
    global region_right
    global region_bottom
    global toggleAim
    global toggleRecoil

    toggleAim = False
    toggleRecoil = False

    path = os.path.join(os.path.dirname(__file__), '../config.ini')
    configFile.read(path)
    print(path)
    ip = configFile.get('communication', 'ip')
    port = int(configFile.get('communication', 'port'))
    com_type = configFile.get('communication', 'type')
    com_port = configFile.get('communication', 'com_port')

    upper_color = configFile.get('screen', 'upper_color').split(',')
    lower_color = configFile.get('screen', 'lower_color').split(',')
    for i in range(0, 3):
        upper_color[i] = int(upper_color[i].strip())
    for i in range(0, 3):
        lower_color[i] = int(lower_color[i].strip())
    upper_color = np.array(upper_color)
    lower_color = np.array(lower_color)

    resolution = configFile.get('screen', 'resolution').split('x')
    resolution = (int(resolution[0]), int(resolution[1]))

    fov = int(configFile.get('screen', 'fov'))
    fps = int(np.floor(1000 / int(configFile.get('screen', 'fps')) + 1))
    offset = int(configFile.get('aim', 'offset'))
    smooth = float(configFile.get('aim', 'smooth'))
    speed = float(configFile.get('aim', 'speed'))
    xMultiplier = float(configFile.get('aim', 'xMultiplier'))
    recoil_mode = configFile.get('recoil', 'mode')
    max_offset = int(configFile.get('recoil', 'max_offset'))
    recoilX = float(configFile.get('recoil', 'recoilX'))
    recoilY = float(configFile.get('recoil', 'recoilY'))
    recoil_recover = int(configFile.get('recoil', 'recover'))

    if not cam:
        cam = dxcam.create(output_color="BGR")
    region_left = (resolution[0] - fov) // 2
    region_top = (resolution[1] - fov) // 2
    region_right = region_left + fov
    region_bottom = region_top + fov
    center = (fov // 2, fov // 2)

    print(f"""Config: 
- Network: {ip}:{port}
- Communication: {com_type}
- Color: LOWER: {lower_color}, UPPER: {upper_color}
- FOV: {fov}
- Offset: {offset}
- Smooth: {smooth}
- Speed: {speed}
- xMultiplier: {xMultiplier}
- Recoil: ({recoilX}, {recoilY})
Config read, all cheats defaulted to off.""")

read_config()
src/cfg.py
vike256/Unibot
import keyboard
from time import sleep

import cfg


delay = 0.2


def check():
    if keyboard.is_pressed('F1'):
        config = cfg.read_config()
        sleep(delay)

    if keyboard.is_pressed('F2'):
        cfg.toggleAim = not cfg.toggleAim
        print("AIM: " + str(cfg.toggleAim))
        sleep(delay)

    if keyboard.is_pressed('F3'):
        cfg.toggleRecoil = not cfg.toggleRecoil
        cfg.recoil_offset = 0
        print("RECOIL: " + str(cfg.toggleRecoil))
        sleep(delay)
src/keybinds.py
vike256/Unibot
import cv2
import time
import numpy as np
import win32api as wapi

import cfg
import keybinds
import mouse
import screen


def main():
    if cfg.com_type == 'socket':
        import socket
        cfg.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    elif cfg.com_type == 'serial':
        import serial
        cfg.board = serial.Serial(cfg.com_port, 115200)

    start_time = time.time()
    previousX = 0
    previousY = 0

    print("Connecting...")

    try:
        if cfg.com_type == 'socket':
            cfg.client.connect((cfg.ip, cfg.port))
        print("Connected")

        while True:
            delta_time = (time.time() - start_time) * 1000
            start_time = time.time()
            x = 0
            y = 0
            
            keybinds.check()

            target = screen.get_target()

            # AIM if mouse left or right down
            if cfg.toggleAim and (wapi.GetAsyncKeyState(0x01) < 0 or wapi.GetAsyncKeyState(0x02) < 0):
                if target is not None:
                    cX, cY = target

                    distanceX = cX - cfg.center[0]
                    distanceY = cY - cfg.center[1] - cfg.recoil_offset
                    x = distanceX * cfg.speed
                    y = distanceY * cfg.speed / cfg.xMultiplier
                    y += cfg.offset

                    #Smoothing
                    x = previousX + cfg.smooth * (x - previousX)
                    y = previousY + cfg.smooth * (y - previousY)
                    previousX = x
                    previousY = y

            # RECOIL
            if cfg.toggleRecoil:
                if delta_time != 0:
                    if cfg.recoil_mode == 'move' and wapi.GetAsyncKeyState(0x01) < 0:
                        x += cfg.recoilX / delta_time
                        y += cfg.recoilY / delta_time
                    elif cfg.recoil_mode == 'offset':
                        if wapi.GetAsyncKeyState(0x01) < 0:
                            if cfg.recoil_offset < cfg.max_offset:
                                cfg.recoil_offset += cfg.recoilY / delta_time
                                if cfg.recoil_offset > cfg.max_offset:
                                    cfg.recoil_offset = cfg.max_offset
                        else:
                            if cfg.recoil_offset > 0:
                                cfg.recoil_offset -= cfg.recoil_recover / delta_time
                                if cfg.recoil_offset < 0:
                                    cfg.recoil_offset = 0


            # TRIGGER
            if wapi.GetAsyncKeyState(0x06) < 0:
                if screen.get_center():
                    mouse.click()

            mouse.move(x, y)
            
            time_spent = (time.time() - start_time) * 1000
            if time_spent < cfg.fps:
                time.sleep((cfg.fps - time_spent) / 1000)
            
            cfg.runtime += delta_time

    except KeyboardInterrupt:
        pass

    except TimeoutError:
        print("Connection attempt failed")

    finally:
        if cfg.com_type == 'socket':
            cfg.client.close()
        print("Closed")


if __name__=="__main__":
    main()
src/main.py
vike256/Unibot
import time
import numpy as np
import cfg

if cfg.com_type == 'none':
    import ctypes

remainderX = 0
remainderY = 0

def move(x, y):
    global remainderX
    global remainderY

    x += remainderX
    y += remainderY

    remainderX, remainderY = (x, y)

    x = int(np.floor(x + 0.5))
    y = int(np.floor(y + 0.5))

    remainderX -= x
    remainderY -= y

    if x != 0 or y != 0:
        command = f"M{x},{y}\r"

        if cfg.com_type == 'socket':
            cfg.client.sendall(command.encode())
        elif cfg.com_type == 'serial':
            cfg.board.write(command.encode())
        elif cfg.com_type == 'none':
            ctypes.windll.user32.mouse_event(0x0001, x, y, 0, 0)

        print(f"{np.floor(cfg.runtime + 0.5):g} Move({x}, {y})", end='')
        if cfg.com_type == 'socket':
            waitForResponse()
        else:
            print('')


def click():
    command = "C\r"
    
    if cfg.com_type == 'socket':
        cfg.client.sendall(command.encode())
    elif cfg.com_type == 'serial':
        cfg.board.write(command.encode())
    elif cfg.com_type == 'none':
        randomDelay = (np.random.randint(40) + 40) / 1000
        ctypes.windll.user32.mouse_event(0x0002, 0, 0, 0, 0)
        time.sleep(randomDelay)
        ctypes.windll.user32.mouse_event(0x0004, 0, 0, 0, 0)

    print(f"{np.floor(cfg.runtime + 0.5):g} Click", end='')
    if cfg.com_type == 'socket':
        waitForResponse()
    elif cfg.com_type == 'none':
        print(f' ({randomDelay * 1000:g} ms)')
    else:
        print('')


def press():
    command = "B1\r"

    if cfg.com_type == 'socket':
        cfg.client.sendall(command.encode())
    elif cfg.com_type == 'serial':
        cfg.board.write(command.encode())
    elif cfg.com_type == 'none':
        ctypes.windll.user32.mouse_event(0x0002, 0, 0, 0, 0)

    print(f"{np.floor(cfg.runtime + 0.5):g} LButton down", end='')
    if cfg.com_type == 'socket':
            waitForResponse()
    else:
        print('')


def release():
    command = "B0\r"
    if cfg.comtype == 'socket':
        cfg.client.sendall(command.encode())
    elif cfg.com_type == 'serial':
        cfg.board.write(command.encode())
    elif cfg.com_type == 'none':
        ctypes.windll.user32.mouse_event(0x0004, 0, 0, 0, 0)


    print(f"{np.floor(cfg.runtime + 0.5):g} LButton up", end='')
    if cfg.com_type == 'socket':
            waitForResponse()
    else:
        print('')


def waitForResponse():
    start = time.time()
    ack = cfg.client.recv(4).decode()
    if ack == "a\r":
        print(f" (ACK {np.floor((time.time() - start) * 1000 + 0.5):g} ms)") # Print latency in milliseconds
src/mouse.py
vike256/Unibot
import cv2
import numpy as np

import cfg


thresh = None


def get_target():
    global thresh
    target = None
    min_distance = float('inf')
    img = cfg.cam.grab(region=(cfg.region_left, int(cfg.region_top - cfg.recoil_offset), cfg.region_right, cfg.region_bottom))
    if img is not None:
        hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        mask = cv2.inRange(hsv, cfg.lower_color, cfg.upper_color)
        kernel = np.ones((3,3), np.uint8)
        dilated = cv2.dilate(mask, kernel, iterations=5)
        thresh = cv2.threshold(dilated, 60, 255, cv2.THRESH_BINARY)[1]
        contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

        if len(contours) != 0:
            for contour in contours:
                M = cv2.moments(contour)
                cX = int(M["m10"] / M["m00"])
                cY = int(M["m01"] / M["m00"])
                distance = np.sqrt((cX - cfg.center[0])**2 + (cY - cfg.center[1] - cfg.recoil_offset)**2)
                if distance < min_distance:
                    min_distance = distance
                    target = (cX, cY + cfg.recoil_offset)
    
    return target

def get_center():
    global thresh

    target = False
    if thresh is None:
        return target

    value = 8
    if thresh[cfg.center[0] + value, cfg.center[1]] == 255:
        if thresh[cfg.center[0] - value, cfg.center[1]] == 255:
            if thresh[cfg.center[0], cfg.center[1] - value] == 255:
                if thresh[cfg.center[0], cfg.center[1] + value] == 255:
                    target = True
    thresh = None
    return target
src/screen.py
vike256/Unibot