Identificación automática de armas Python Apex Legends y registro de proceso completo de presión de armas

Directorio de publicaciones del blog

Directorio de artículos


Este artículo es el estudio y la práctica de los siguientes artículos de referencia.

[Original] Reconocimiento automático de armas de fuego del juego FPS + pistola de presión (tomando PUBG como ejemplo)
[Reimpresión] Reconocimiento automático de armas de fuego del juego FPS + pistola de presión (tomando PUBG como ejemplo)

Preparación ambiental

Python entorno de desarrollo de Windows para construir

conda create -n apex python=3.9

Manipular el teclado y el ratón.

Dado que PUBG bloquea otras entradas del mouse fuera del controlador de hardware, no podemos controlar directamente la operación del mouse en el juego a través del script py. Para realizar el mouse hacia abajo en el juego, usé el controlador de mouse Logitech (ghub), y py le pasé la operación de comando a ghub llamando al archivo de biblioteca de enlaces de ghub, y finalmente me di cuenta del uso de comandos de mouse controlados por hardware para entrada al juego, por lo tanto, omita las restricciones de entrada del mouse del juego. Vale la pena mencionar que solo pasamos las instrucciones al controlador de Logitech a través del código py llamando a la interfaz de la biblioteca de enlaces, que no tiene nada que ver con el mouse real utilizado, por lo que incluso si el usuario usa Razer, Zhuowei, Shuangfeiyan, etc. El mouse, no tiene ningún efecto sobre el código de abajo.

Biblioteca de enlaces de instalación de controladores, preparación de códigos de carga y pruebas fuera del juego

El controlador de Logitech utiliza LGS_9.02.65_X64 (busque los recursos para instalarlo usted mismo, la nueva versión del controlador de Logitech en el sitio web oficial no encuentra el archivo de biblioteca de enlaces correspondiente). El archivo de biblioteca de enlaces se puede encontrar en el enlace del proyecto . A continuación se muestra el código para cargar la biblioteca de enlaces.

Los controladores de Logitech se dividen en LGS (antiguo) y GHub (nuevo), se debe instalar la versión especificada del controlador LGS (si GHub está instalado, es posible que deba desinstalarse); de lo contrario, informará que no está instalado o la inicialización es exitosa pero la llamada no es válida.

LGS_9.02.65_x64_Logitech.exe, descargar desde disco de red

mouse.dispositivo.lgs.dll

try:
    gm = CDLL(r'./mouse.device.lgs.dll')
    gmok = gm.device_open() == 1
    if not gmok:
        print('未安装ghub或者lgs驱动!!!')
    else:
        print('初始化成功!')
except FileNotFoundError:
    print('缺少文件')

Después de instalar el controlador, tendrá efecto inmediatamente sin reiniciar la computadora.Desafortunadamente, no hay documentación correspondiente para los métodos en este archivo dll, por lo que solo podemos adivinar los parámetros.

kit de herramientas.py

import time
from ctypes import CDLL

import win32api  # conda install pywin32


try:
    driver = CDLL(r'mouse.device.lgs.dll')  # 在Python的string前面加上‘r’, 是为了告诉编译器这个string是个raw string(原始字符串),不要转义backslash(反斜杠) '\'
    ok = driver.device_open() == 1
    if not ok:
        print('初始化失败, 未安装lgs/ghub驱动')
except FileNotFoundError:
    print('初始化失败, 缺少文件')


class Mouse:

    @staticmethod
    def move(x, y, absolute=False):
        if ok:
            mx, my = x, y
            if absolute:
                ox, oy = win32api.GetCursorPos()
                mx = x - ox
                my = y - oy
            driver.moveR(mx, my, True)

    @staticmethod
    def down(code):
        if ok:
            driver.mouse_down(code)

    @staticmethod
    def up(code):
        if ok:
            driver.mouse_up(code)

    @staticmethod
    def click(code):
        """
        :param code: 1:左键, 2:中键, 3:右键, 4:侧下键, 5:侧上键, 6:DPI键
        :return:
        """
        if ok:
            driver.mouse_down(code)
            driver.mouse_up(code)


class Keyboard:

    @staticmethod
    def press(code):
        if ok:
            driver.key_down(code)

    @staticmethod
    def release(code):
        if ok:
            driver.key_up(code)

    @staticmethod
    def click(code):
        """
        :param code: 'a'-'z':A键-Z键, '0'-'9':0-9, 其他的没猜出来
        :return:
        """
        if ok:
            driver.key_down(code)
            driver.key_up(code)

pruebas en el juego

Después de probarlo en el juego, funciona, pero no está permitido, supongo que puede estar relacionado con la sensibilidad del mouse/FOV en el juego, etc.

from toolkit import Mouse
import pynput  # conda install pynput

def onClick(x, y, button, pressed):
    if not pressed:
        if pynput.mouse.Button.x2 == button:
            Mouse.move(100, 100)


mouseListener = pynput.mouse.Listener(on_click=onClick)
mouseListener.start()
mouseListener.join()

Monitoreo de teclado y mouse

Como se mencionó anteriormente, para realizar la pistola de presión, es necesario identificar varios accesorios y estados. Entonces, antes de escribir la función de reconocimiento, debemos resolver el problema de cuándo reconocer. Sin duda, es una gran sobrecarga identificar la detección continua de subprocesos múltiples/procesos múltiples, por lo que es necesario monitorear el estado del teclado y el mouse. Una solicitud de identificación correspondiente específica se activa solo cuando se presiona una tecla específica.

El gancho que uso aquí es Pynput, y otras bibliotecas que se pueden usar son Pyhook3

Descripción de Pynput

def onClick(x, y, button, pressed):
    print(f'button {
      
      button} {
      
      "pressed" if pressed else "released"} at ({
      
      x},{
      
      y})')
    if pynput.mouse.Button.left == button:
        return False  # 正常不要返回False, 这样会结束监听并停止监听线程, 在关闭程序前返回False就好了
listener = pynput.mouse.Listener(on_click=onClick)
listener.start()


def onRelease(key):
    print(f'{
      
      key} released')
    if key == pynput.keyboard.Key.end:
        return False  # 正常不要返回False, 这样会结束监听并停止监听线程, 在关闭程序前返回False就好了
listener = pynput.keyboard.Listener(on_release=onRelease)
listener.start()

Tenga en cuenta que al depurar el método de devolución de llamada, no rompa el punto, no rompa el punto, no rompa el punto, esto bloqueará el IO y hará que el mouse y el teclado fallen.

Las funciones de on_press y on_release (on_key_press, on_key_release) están vinculadas en el Listener. Cuando devuelven False, finalizan el monitoreo. Lo mismo es cierto para las funciones de monitoreo del mouse a continuación, así que no solo devuelva False.

Las teclas especiales del teclado se escriben en keyboard.Key.tab, y las teclas ordinarias se escriben en keyboard.KeyCode.from_char('c').

Algunas teclas no saben escribir, puedes print(key)revisar ortografía

Hay un pozo muy aquí Los parámetros de on_press y on_release solo pueden tener una tecla, y esta tecla es la tecla presionada en el teclado correspondiente. Pero esto no es suficiente para satisfacer nuestras necesidades, ya que debemos modificar el semáforo dentro de la función gancho cuando se presiona el botón especificado, pero debido a la limitación de parámetros, no podemos pasar el semáforo a la función, aquí también pensé en durante mucho tiempo, y finalmente pensé en usar la función anidada para resolver este problema.

Además, la función de gancho en sí misma está bloqueando. Es decir, durante la ejecución de la función de enganche, no se pueden introducir las operaciones normales de teclado/ratón del usuario. Por lo tanto, la función de gancho debe escribirse como una operación limitada (es decir, el código con complejidad de tiempo O(1), lo que significa que la sobrecarga de tiempo, como la identificación de accesorios en la mochila y las armas de fuego, y la pistola de presión del mouse mencionado a continuación es relativamente grande o las operaciones de larga duración no son adecuadas para escribir en funciones gancho. Esto también explica por qué cuando se detecta la pestaña (abrir la mochila) y el botón izquierdo del mouse, el semáforo solo se cambia, y luego estas tareas se dejan para que las realicen otros procesos.

identificación de armas

inserte la descripción de la imagen aquí

Cómo determinar fácil y eficientemente si estás en el juego o no

Primero, determine si la ventana del juego está en primer plano y luego determine si la interfaz del arma se mantiene en el juego.

Encuentre algunos puntos característicos para juzgar el color, la esquina superior izquierda de la barra de salud y la esquina inferior izquierda del cuadro de elementos de supervivencia.

En general, el punto que se puede usar para la selección de color, su color RGB es el mismo, el color de este punto es muy estable

Originalmente pensé que no tomaría más de 1 ms seleccionar un color en la pantalla, pero nunca esperé que tomara 1-10 ms seleccionar un color, lo cual es extremadamente ineficiente y no hay otro método elegante.

Cómo juzgar simple y eficientemente el estado de la mochila sin arma/arma 1/arma 2

inserte la descripción de la imagen aquí

Mire el color de la parte con un círculo rojo en el armazón del arma, el gris significa que no hay arma, los diferentes colores hacia arriba y hacia abajo indican el uso del arma No. 2, y el mismo color hacia arriba y hacia abajo significa que el arma No. 1 es usó

Cómo determinar fácil y eficientemente el tipo de bala del arma ligera/pesada/energía/francotirador/disparo/lanzamiento aéreo

Porque las armas de diferentes tipos de balas tienen diferentes colores de borde, por lo que se pueden juntar con las anteriores, y el mismo punto puede determinar directamente el estado de la mochila y el tipo de balas.

Cómo determinar fácil y eficientemente el nombre de un arma

Sobre la base de la clasificación, determine la posición para verificar el color (posición 1/posición 2) por el estado de la mochila, reduzca el rango de juicio por la categoría de bala del arma y encuentre un punto blanco puro en el nombre de cada arma para asegúrese de que este punto solo esta arma sea de color blanco puro, y luego compárelos uno por uno

Cómo juzgar fácil y eficientemente el modo de arma completamente automático/ráfaga/único

inserte la descripción de la imagen aquí
Las únicas armas que necesitan presionar el arma son las totalmente automáticas y semiautomáticas. Un solo disparo no necesita presionar el arma (es posible hacer un solo disparo automático más adelante, que se considerará cuando llegue el momento), y el arma y el francotirador no necesita presionar el arma.

Por lo tanto, debe encontrar un punto que pueda distinguir los tres modos (el color de este punto es diferente pero estable en diferentes modos), y este punto no puede verse afectado por las marcas especiales de paz y triple.

Al principio encontré un punto que no era blanco puro, luego descubrí que el color de este punto se verá afectado por el color de fondo, que no es constante, al final abandoné la idea de distinguiendo el modo con un punto, y adoptó un punto blanco puro estable, para garantizar que el punto sea blanco puro solo en este modo, y no blanco puro en otros modos

Cómo determinar fácil y eficientemente si tienes un arma

Es imposible juzgar por el momento, no hay un punto fijo que pueda distinguir claramente entre las dos situaciones cuando se guarda el arma y se sostiene el arma.

Algunas armas se pueden juzgar por la marca [V], porque están incompletas, no se usarán primero

También puede establecer la marca monitoreando y presionando la tecla [3] (la operación de retraer el arma), otras operaciones eliminan la marca y luego leen la marca para determinar si tiene un arma, pero no es elegante, por lo que no lo uses primero

La conclusión actual es que cuando se usa un puño, la mira es una cruz cuadrada grande, y cuando se usa un arma, es una cruz redonda. Pero usar un puño no significa no sostener un arma.

Cómo determinar fácil y eficientemente si la revista está vacía

inserte la descripción de la imagen aquí
El número de balas en el cargador es principalmente de dos dígitos (LSTAR puede tener tres dígitos), así que solo confirme que el dígito de las decenas no es 0, puede considerarse que no está vacío, el dígito de las decenas es 0 y el dígito de las unidades es 0, es puede considerarse vacío

  • El punto de las decenas está en el medio del número, 1-9 son de color blanco puro, 0 es gris. Tenga en cuenta que este gris no es un color fijo, el color cambiará a medida que cambie el fondo.
  • El punto del dígito de la unidad, en el extremo izquierdo de la barra diagonal en el medio del número 0, este punto es blanco puro, y cuando otro 1-9, este punto no es blanco puro

Cuándo activar el reconocimiento

  • Pulsa el botón derecho del ratón para identificar el arma. No interfiere con la función del botón original del juego.
  • 1/2/3/E/V/R/Tab/Esc/Alt soltar tecla, identificar arma
  • Liberación de la tecla de inicio, interruptor de palanca
  • Finalizar liberación de tecla, finalizar el programa.

algunos detalles

  • A través de las pruebas, se encontró que el intervalo de disparo de todas las armas es superior a 50 milisegundos, por lo que al presionar el arma, puede realizar algunas operaciones dentro de los 50 milisegundos, como juzgar si el cargador está vacío, para evitar disparar el arma de presión

ideas de pistola de presion

Hay 3 ideas para la pistola de presión de Apex, porque la balística de las diferentes armas de Apex parece estar arreglada, ¿no hay un valor aleatorio? ¿Y otros juegos también?

  • El movimiento hacia la izquierda y hacia la derecha contrarresta el retroceso horizontal, y el movimiento hacia abajo contrarresta el retroceso vertical.Este método es simple, pero la imagen se moverá y el efecto no es muy bueno.
  • Pruebe los datos de retroceso del arma en diferentes condiciones de acuerdo con los accesorios del arma, etc., y luego haga el desplazamiento inverso.
    Puede usar una forma engañosa de hacer solo el desplazamiento inverso sin los accesorios y ahorrarse la molestia de encontrar accesorios.
    Este método es demasiado difícil y problemático. Sí, pero si se hace bien, es básicamente una línea y es escandalosamente fuerte.
  • También está la muy popular detección de objetivos de IA (yolov5), que también traté de hacer. El entorno estaba configurado, pero se quedó atascado en el medio. Primero, después de todo, Python es un interés, muchos conceptos básicos no están en su lugar. , y el conocimiento profesional relevante está incluso en blanco. Como referencia El contenido también es desigual, lo que hace que los parámetros de detección y capacitación sean muy vagos. El segundo es la recopilación de conjuntos de datos. Encontré algunos en Internet y los hice yo mismo, pero no todavía es un poco. No te preocupes, búscalo despacio. Si quieres conseguir un buen efecto, necesitas miles de fotos...

Organización de datos

Datos del arma, agrupados por tipo de bala, cada miembro del grupo especifica el número de serie, el nombre, los parámetros de presión del arma y otra información

Datos de configuración, agrupados por resolución y luego clasificados por si está en el juego, si hay un arma, ubicación del arma, tipo de bala del arma, índice del arma, etc.

Datos de señal, tiempo de ejecución del programa, comunicación de subprocesos entre procesos

La implementación de la primera etapa puede identificar automáticamente todas las armas.

En la actualidad, después de la prueba, una ola de reconocimiento es de aproximadamente 60 a 70 milisegundos, y no excederá los milisegundos 100. El tiempo principal se dedica a la función de selección de color (1-10 ms), y el rendimiento es suficiente.

Mi configuración: AMD R7 2700x, Nvidia RTX 2080, resolución 3440*1440

cfg.py


mode = 'mode'
name = 'name'
game = 'game'
data = 'data'
pack = 'pack'  # 背包
color = 'color'
point = 'point'
index = 'index'
bullet = 'bullet'  # 子弹
differ = 'differ'
positive = 'positive'  # 肯定的
negative = 'negative'  # 否定的

# 检测数据
detect = {
    
    
    "3440:1440": {
    
    
        game: [  # 判断是否在游戏中
            {
    
    
                point: (236, 1344),  # 点的坐标, 血条左上角
                color: 0x00FFFFFF  # 点的颜色, 255, 255, 255
            },
            {
    
    
                point: (2692, 1372),  # 生存物品右下角
                color: 0x959595  # 149, 149, 149
            }
        ],
        pack: {
    
      # 背包状态, 有无武器, 选择的武器
            point: (2900, 1372),  # 两把武器时, 1号武器上面边框分界线的上半部分, y+1 就是1号武器上面边框分界线的下半部分
            color: 0x808080,  # 无武器时, 灰色, 128, 128, 128
            '0x447bb4': 1,  # 轻型弹药武器, 子弹类型: 1/2/3/4/5/6/None(无武器)
            '0x839b54': 2,  # 重型弹药武器
            '0x3da084': 3,  # 能量弹药武器
            '0xce5f6e': 4,  # 狙击弹药武器
            '0xf339b': 5,  # 霰弹枪弹药武器
            '0x5302ff': 6,  # 空投武器
        },
        mode: {
    
      # 武器模式, 全自动/半自动/单发/其他
            point: (3148, 1349),
            '0xf8f8f8': 1,  # 全自动
            '0xfefefe': 2  # 半自动
        },
        name: {
    
      # 武器名称判断
            color: 0x00FFFFFF,
            '1': {
    
      # 1号武器
                '1': [  # 轻型弹药武器
                    (2959, 1386),  # 1: RE-45 自动手枪
                    (2970, 1385),  # 2: 转换者冲锋枪
                    (2972, 1386),  # 3: R-301 卡宾枪
                    (2976, 1386),  # 4: R-99 冲锋枪
                    (2980, 1386),  # 5: P2020 手枪
                    (2980, 1384),  # 6: 喷火轻机枪
                    (2987, 1387),  # 7: G7 侦查枪
                    (3015, 1386),  # 8: CAR (轻型弹药)
                ],
                '2': [  # 重型弹药武器
                    (2957, 1385),  # 1: 赫姆洛克突击步枪
                    (2982, 1385),  # 2: 猎兽冲锋枪
                    (2990, 1393),  # 3: 平行步枪
                    (3004, 1386),  # 4: 30-30
                    (3015, 1386),  # 5: CAR (重型弹药)
                ],
                '3': [  # 能量弹药武器
                    (2955, 1386),  # 1: L-STAR能量机枪
                    (2970, 1384),  # 2: 三重式狙击枪
                    (2981, 1385),  # 3: 电能冲锋枪
                    (2986, 1384),  # 4: 专注轻机枪
                    (2980, 1384),  # 5: 哈沃克步枪
                ],
                '4': [  # 狙击弹药武器
                    (2969, 1395),  # 1: 哨兵狙击步枪
                    (2999, 1382),  # 2: 充能步枪
                    (2992, 1385),  # 3: 辅助手枪
                    (3016, 1383),  # 4: 长弓
                ],
                '5': [  # 霰弹枪弹药武器
                    (2957, 1384),  # 1: 和平捍卫者霰弹枪
                    (2995, 1382),  # 2: 莫桑比克
                    (3005, 1386),  # 3: EVA-8
                ],
                '6': [  # 空投武器
                    (2958, 1384),  # 1: 克雷贝尔狙击枪
                    (2983, 1384),  # 2: 敖犬霰弹枪
                    (3003, 1383),  # 3: 波塞克
                    (3014, 1383),  # 4: 暴走
                ]
            },
            '2': {
    
    
                differ: 195  # 直接用1的坐标, 横坐标右移195就可以了
            }
        }
    },
    "2560:1440": {
    
    

    },
    "2560:1080": {
    
    

    },
    "1920:1080": {
    
    

    }
}

# 武器数据
weapon = {
    
    
    '1': {
    
      # 轻型弹药武器
        '1': {
    
    
            name: 'RE-45 自动手枪',
        },
        '2': {
    
    
            name: '转换者冲锋枪',
        },
        '3': {
    
    
            name: 'R-301 卡宾枪',
        },
        '4': {
    
    
            name: 'R-99 冲锋枪',
        },
        '5': {
    
    
            name: 'P2020 手枪',
        },
        '6': {
    
    
            name: '喷火轻机枪',
        },
        '7': {
    
    
            name: 'G7 侦查枪',
        },
        '8': {
    
    
            name: 'CAR (轻型弹药)',
        }
    },
    '2': {
    
      # 重型弹药武器
        '1': {
    
    
            name: '赫姆洛克突击步枪',
        },
        '2': {
    
    
            name: '猎兽冲锋枪',
        },
        '3': {
    
    
            name: '平行步枪',
        },
        '4': {
    
    
            name: '30-30',
        },
        '5': {
    
    
            name: 'CAR (重型弹药)',
        }
    },
    '3': {
    
      # 能量弹药武器
        '1': {
    
    
            name: 'L-STAR能量机枪',
        },
        '2': {
    
    
            name: '三重式狙击枪',
        },
        '3': {
    
    
            name: '电能冲锋枪',
        },
        '4': {
    
    
            name: '专注轻机枪',
        },
        '5': {
    
    
            name: '哈沃克步枪',
        },
    },
    '4': {
    
      # 狙击弹药武器
        '1': {
    
    
            name: '哨兵狙击步枪',
        },
        '2': {
    
    
            name: '充能步枪',
        },
        '3': {
    
    
            name: '辅助手枪',
        },
        '4': {
    
    
            name: '长弓',
        },
    },
    '5': {
    
      # 霰弹弹药武器
        '1': {
    
    
            name: '和平捍卫者霰弹枪',
        },
        '2': {
    
    
            name: '莫桑比克',
        },
        '3': {
    
    
            name: 'EVA-8',
        },
    },
    '6': {
    
      # 空投武器
        '1': {
    
    
            name: '克雷贝尔狙击枪',
        },
        '2': {
    
    
            name: '敖犬霰弹枪',
        },
        '3': {
    
    
            name: '波塞克',
        },
        '4': {
    
    
            name: '暴走',
        },
    }
}

kit de herramientas.py

import mss  # pip install mss
import ctypes

from ctypes import CDLL

import cfg
from cfg import detect, weapon

# 全局 dll
user32 = ctypes.windll.user32
gdi32 = ctypes.windll.gdi32
hdc = user32.GetDC(None)

try:
    driver = CDLL(r'mouse.device.lgs.dll')  # 在Python的string前面加上‘r’, 是为了告诉编译器这个string是个raw string(原始字符串),不要转义backslash(反斜杠) '\'
    ok = driver.device_open() == 1
    if not ok:
        print('初始化失败, 未安装lgs/ghub驱动')
except FileNotFoundError:
    print('初始化失败, 缺少文件')


class Mouse:

    @staticmethod
    def point():
        return user32.GetCursorPos()

    @staticmethod
    def move(x, y, absolute=False):
        if ok:
            mx, my = x, y
            if absolute:
                ox, oy = user32.GetCursorPos()
                mx = x - ox
                my = y - oy
            driver.moveR(mx, my, True)

    @staticmethod
    def moveHumanoid(x, y, absolute=False):
        """
        仿真移动(还没做好)
        """
        if ok:
            ox, oy = user32.GetCursorPos()  # 原鼠标位置
            mx, my = x, y  # 相对移动距离
            if absolute:
                mx = x - ox
                my = y - oy
            tx, ty = ox + mx, oy + my
            print(f'({
      
      ox},{
      
      oy}), ({
      
      tx},{
      
      ty}), x:{
      
      mx},y:{
      
      my}')
            # 以绝对位置方式移动(防止相对位置丢失精度)
            adx, ady = abs(mx), abs(my)
            if adx <= ady:
                # 水平方向移动的距离短
                for i in range(1, adx):
                    ix = i if mx > 0 else -i
                    temp = int(ady / adx * abs(ix))
                    iy = temp if my > 0 else -temp
                    Mouse.move(ox + ix, oy + iy, absolute=True)
                    # time.sleep(0.001)
            else:
                # 垂直方向移动的距离短
                for i in range(1, ady):
                    iy = i if my > 0 else -i
                    temp = int(adx / ady * abs(iy))
                    ix = temp if mx > 0 else -temp
                    Mouse.move(ox + ix, oy + iy, absolute=True)
                    # time.sleep(0.001)

    @staticmethod
    def down(code):
        if ok:
            driver.mouse_down(code)

    @staticmethod
    def up(code):
        if ok:
            driver.mouse_up(code)

    @staticmethod
    def click(code):
        """
        :param code: 1:左键, 2:中键, 3:右键, 4:侧下键, 5:侧上键, 6:DPI键
        :return:
        """
        if ok:
            driver.mouse_down(code)
            driver.mouse_up(code)


class Keyboard:

    @staticmethod
    def press(code):
        if ok:
            driver.key_down(code)

    @staticmethod
    def release(code):
        if ok:
            driver.key_up(code)

    @staticmethod
    def click(code):
        """
        键盘按键函数中,传入的参数采用的是键盘按键对应的键码
        :param code: 'a'-'z':A键-Z键, '0'-'9':0-9, 其他的没猜出来
        :return:
        """
        if ok:
            driver.key_down(code)
            driver.key_up(code)


class Monitor:
    """
    显示器
    """
    sct = mss.mss()

    @staticmethod
    def grab(region):
        """
        region: tuple, (left, top, width, height)
        pip install mss
        """
        left, top, width, height = region
        return Monitor.sct.grab(monitor={
    
    'left': left, 'top': top, 'width': width, 'height': height})

    @staticmethod
    def pixel(x, y):
        """
        效率很低且不稳定, 单点检测都要耗时1-10ms
        获取颜色, COLORREF 格式, 0x00FFFFFF
        结果是int,
        可以通过 print(hex(color)) 查看十六进制值
        可以通过 print(color == 0x00FFFFFF) 进行颜色判断
        """
        # hdc = user32.GetDC(None)
        return gdi32.GetPixel(hdc, x, y)

    class Resolution:
        """
        分辨率
        """

        @staticmethod
        def display():
            """
            显示分辨率
            """
            w = user32.GetSystemMetrics(0)
            h = user32.GetSystemMetrics(1)
            return w, h

        @staticmethod
        def virtual():
            """
            多屏幕组合的虚拟显示器分辨率
            """
            w = user32.GetSystemMetrics(78)
            h = user32.GetSystemMetrics(79)
            return w, h

        @staticmethod
        def physical():
            """
            物理分辨率
            """
            # hdc = user32.GetDC(None)
            w = gdi32.GetDeviceCaps(hdc, 118)
            h = gdi32.GetDeviceCaps(hdc, 117)
            return w, h


class Game:
    """
    游戏工具
    """

    @staticmethod
    def game():
        """
        是否在游戏内
        太耗时了, 所以不能调的多了
        """
        w, h = Monitor.Resolution.display()
        data = detect.get(f'{
      
      w}:{
      
      h}').get(cfg.game)
        for item in data:
            x, y = item.get(cfg.point)
            if Monitor.pixel(x, y) != item.get(cfg.color):
                return False
        return True

    @staticmethod
    def index():
        """
        武器索引和子弹类型索引
        :return: 武器位索引, 1:1号位, 2:2号位, None:无武器, 拳头(这个暂时无法判断)
                 子弹类型索引, 1:轻型, 2:重型, 3:能量, 4:狙击, 5:霰弹, 6:空投, None:无武器
        """
        w, h = Monitor.Resolution.display()
        data = detect.get(f'{
      
      w}:{
      
      h}').get(cfg.pack)
        x, y = data.get(cfg.point)
        color = Monitor.pixel(x, y)
        if data.get(cfg.color) == color:
            return None, None
        else:
            bullet = data.get(hex(color))
            return (1, bullet) if color == Monitor.pixel(x, y + 1) else (2, bullet)

    @staticmethod
    def weapon(index, bullet):
        """
        通过武器位和子弹类型识别武器, 参考:config.detect.name
        :param index: 武器位, 1:1号位, 2:2号位
        :param bullet: 子弹类型, 1:轻型, 2:重型, 3:能量, 4:狙击, 5:霰弹, 6:空投
        :return:
        """
        w, h = Monitor.Resolution.display()
        data = detect.get(f'{
      
      w}:{
      
      h}').get(cfg.name)
        color = data.get(cfg.color)
        if index == 1:
            lst = data.get(str(index)).get(str(bullet))
            for i in range(len(lst)):
                x, y = lst[i]
                if color == Monitor.pixel(x, y):
                    return i + 1
        elif index == 2:
            differ = data.get(str(index)).get(cfg.differ)
            lst = data.get(str(1)).get(str(bullet))
            for i in range(len(lst)):
                x, y = lst[i]
                if color == Monitor.pixel(x + differ, y):
                    return i + 1
        return None

    @staticmethod
    def mode():
        """
        武器模式
        :return:  1:全自动, 2:半自动, None:其他
        """
        w, h = Monitor.Resolution.display()
        data = detect.get(f'{
      
      w}:{
      
      h}').get(cfg.mode)
        x, y = data.get(cfg.point)
        color = Monitor.pixel(x, y)
        return data.get(hex(color))

    @staticmethod
    def detect():
        """
        决策是否需要压枪, 向信号量写数据
        """
        if Game.game() is False:
            print('not in game')

            return
        index, bullet = Game.index()
        if (index is None) | (bullet is None):
            print('no weapon')

            return
        if Game.mode() is None:
            print('not in full auto or semi auto mode')

            return
        arms = Game.weapon(index, bullet)
        if arms is None:
            print('detect weapon failure')

            return
        # 检测通过, 需要压枪
        print(weapon.get(str(bullet)).get(str(arms)).get(cfg.name))
        return weapon.get(str(bullet)).get(str(arms)).get(cfg.name)

apex.py

import time

import pynput  # conda install pynput

import toolkit

ExitFlag = False


def down(x, y, button, pressed):
    global ExitFlag
    if ExitFlag:
        print(ExitFlag)
        return False  # 结束监听线程
    if pressed:  # 按下
        if pynput.mouse.Button.right == button:
            toolkit.Game.detect()


mouseListener = pynput.mouse.Listener(on_click=down)
mouseListener.start()


def release(key):
    if key == pynput.keyboard.Key.end:
        print('end')
        global ExitFlag
        ExitFlag = True
        return False
    if key == pynput.keyboard.KeyCode.from_char('1'):
        toolkit.Game.detect()
    elif key == pynput.keyboard.KeyCode.from_char('2'):
        toolkit.Game.detect()
    elif key == pynput.keyboard.KeyCode.from_char('3'):
        toolkit.Game.detect()
    elif key == pynput.keyboard.KeyCode.from_char('e'):
        toolkit.Game.detect()
    elif key == pynput.keyboard.KeyCode.from_char('v'):
        toolkit.Game.detect()


keyboardListener = pynput.keyboard.Listener(on_release=release)
keyboardListener.start()
keyboardListener.join()

inserte la descripción de la imagen aquí

La segunda etapa se da cuenta de que los parámetros de agitación de la pistola correspondientes se pueden usar automáticamente para ejecutar la pistola de presión.

inserte la descripción de la imagen aquí

  • Cuanto mayor sea la sensibilidad del mouse en el juego, más fácil es agitar el arma y el efecto es mejor, pero si lo abres a 5, te sentirás un poco mareado.
  • Cuanto mayor sea la sensibilidad del mouse en el juego, menor será la necesidad de configurar los píxeles fluctuantes en el código. Por ejemplo, si la sensibilidad es 5, la fluctuación es de 2 píxeles.
  • Sacudir el arma puede reducir el retroceso, pero no puede eliminarlo por completo, por lo que debe moverse en la dirección correspondiente.

Con una sensibilidad de 2.5, 301 usa los siguientes parámetros, de 20 a 30 metros está bien, 50 metros y el triple se duplicará el efecto, lo cual es muy malo. El arma con mayor retroceso, los primeros disparos son fáciles de saltar demasiado alto. y el siguiente disparo es demasiado alto. La presión puede ser mayor

Además, el retraso debería ser menor. Aquí tengo un retraso de conexión desnudo de más de 300. A menudo disparo balas y la sangre se reduce después de medio segundo, por lo que es difícil medir la precisión.

Las armas de energía, enfoque y havok, precalentamiento y turbo tienen un gran impacto, no importa aquí, será pronto.

total = 0  # 总计时 ms
delay = 1  # 延迟 ms
pixel = 4  # 抖动像素
while True:
    if not data[fire]:
        break
    # 下压
    if total < 30:
        toolkit.Mouse.move(0, 5)
        time.sleep(delay / 1000)
        total += delay
    else:
        toolkit.Mouse.move(0, 1)
        time.sleep(delay / 1000)
        total += delay
    # 抖枪
   	toolkit.Mouse.move(pixel, 0)
    time.sleep(delay / 1000)
    total += delay
    toolkit.Mouse.move(-pixel, 0)
    time.sleep(delay / 1000)
    total += delay

cfg.py

mode = 'mode'
name = 'name'
game = 'game'
data = 'data'
pack = 'pack'  # 背包
color = 'color'
point = 'point'
index = 'index'
shake = 'shake'
speed = 'speed'
count = 'count'
switch = 'switch'
bullet = 'bullet'  # 子弹
differ = 'differ'
suppress = 'suppress'
strength = 'strength'
positive = 'positive'  # 肯定的
negative = 'negative'  # 否定的

# 检测数据
detect = {
    
    
    "3440:1440": {
    
    
        game: [  # 判断是否在游戏中
            {
    
    
                point: (236, 1344),  # 点的坐标, 血条左上角
                color: 0x00FFFFFF  # 点的颜色, 255, 255, 255
            },
            {
    
    
                point: (2692, 1372),  # 生存物品右下角
                color: 0x959595  # 149, 149, 149
            }
        ],
        pack: {
    
      # 背包状态, 有无武器, 选择的武器
            point: (2900, 1372),  # 两把武器时, 1号武器上面边框分界线的上半部分, y+1 就是1号武器上面边框分界线的下半部分
            color: 0x808080,  # 无武器时, 灰色, 128, 128, 128
            '0x447bb4': 1,  # 轻型弹药武器, 子弹类型: 1/2/3/4/5/6/None(无武器)
            '0x839b54': 2,  # 重型弹药武器
            '0x3da084': 3,  # 能量弹药武器
            '0xce5f6e': 4,  # 狙击弹药武器
            '0xf339b': 5,  # 霰弹枪弹药武器
            '0x5302ff': 6,  # 空投武器
        },
        mode: {
    
      # 武器模式, 全自动/半自动/单发/其他
            color: 0x00FFFFFF,
            '1': (3151, 1347),  # 全自动
            '2': (3171, 1351),  # 半自动
        },
        name: {
    
      # 武器名称判断
            color: 0x00FFFFFF,
            '1': {
    
      # 1号武器
                '1': [  # 轻型弹药武器
                    (2959, 1386),  # 1: RE-45 自动手枪
                    (2970, 1385),  # 2: 转换者冲锋枪
                    (2972, 1386),  # 3: R-301 卡宾枪
                    (2976, 1386),  # 4: R-99 冲锋枪
                    (2980, 1386),  # 5: P2020 手枪
                    (2980, 1384),  # 6: 喷火轻机枪
                    (2987, 1387),  # 7: G7 侦查枪
                    (3015, 1386),  # 8: CAR (轻型弹药)
                ],
                '2': [  # 重型弹药武器
                    (2957, 1385),  # 1: 赫姆洛克突击步枪
                    (2982, 1385),  # 2: 猎兽冲锋枪
                    (2990, 1393),  # 3: 平行步枪
                    (3004, 1386),  # 4: 30-30
                    (3015, 1386),  # 5: CAR (重型弹药)
                ],
                '3': [  # 能量弹药武器
                    (2955, 1386),  # 1: L-STAR能量机枪
                    (2970, 1384),  # 2: 三重式狙击枪
                    (2981, 1385),  # 3: 电能冲锋枪
                    (2986, 1384),  # 4: 专注轻机枪
                    (2980, 1384),  # 5: 哈沃克步枪
                ],
                '4': [  # 狙击弹药武器
                    (2969, 1395),  # 1: 哨兵狙击步枪
                    (2999, 1382),  # 2: 充能步枪
                    (2992, 1385),  # 3: 辅助手枪
                    (3016, 1383),  # 4: 长弓
                ],
                '5': [  # 霰弹枪弹药武器
                    (2957, 1384),  # 1: 和平捍卫者霰弹枪
                    (2995, 1382),  # 2: 莫桑比克
                    (3005, 1386),  # 3: EVA-8
                ],
                '6': [  # 空投武器
                    (2958, 1384),  # 1: 克雷贝尔狙击枪
                    (2983, 1384),  # 2: 敖犬霰弹枪
                    (3003, 1383),  # 3: 波塞克
                    (3014, 1383),  # 4: 暴走
                ]
            },
            '2': {
    
    
                differ: 195  # 直接用1的坐标, 横坐标右移195就可以了
            }
        }
    },
    "2560:1440": {
    
    

    },
    "2560:1080": {
    
    

    },
    "1920:1080": {
    
    

    }
}

# 武器数据
weapon = {
    
    
    '1': {
    
      # 轻型弹药武器
        '1': {
    
    
            name: 'RE-45 自动手枪',  # 全程往右飘
            shake: {
    
    
                speed: 80,
                count: 10,
                strength: 5,
            }
        },
        '2': {
    
    
            name: '转换者冲锋枪',
            shake: {
    
    
                speed: 100,
                count: 10,
                strength: 7,
            }
        },
        '3': {
    
    
            name: 'R-301 卡宾枪',
            shake: {
    
    
                speed: 74,  # 74ms打一发子弹
                count: 6,  # 压制前6发
                strength: 5,  # 压制的力度(下移的像素)
            },
            suppress: {
    
    
                speed: 74,
            }
        },
        '4': {
    
    
            name: 'R-99 冲锋枪',
            shake: {
    
    
                speed: 55.5,
                count: 13,
                strength: 8,
            }
        },
        '5': {
    
    
            name: 'P2020 手枪',
        },
        '6': {
    
    
            name: '喷火轻机枪',
            shake: {
    
    
                speed: 111,
                count: 8,
                strength: 5,
            }
        },
        '7': {
    
    
            name: 'G7 侦查枪',
        },
        '8': {
    
    
            name: 'CAR (轻型弹药)',
            shake: {
    
    
                speed: 64.5,
                count: 10,
                strength: 7,
            }
        }
    },
    '2': {
    
      # 重型弹药武器
        '1': {
    
    
            name: '赫姆洛克突击步枪',
            shake: {
    
    
                speed: 50,
                count: 3,
                strength: 6,
            }
        },
        '2': {
    
    
            name: '猎兽冲锋枪',
            shake: {
    
    
                speed: 50,
                count: 5,
                strength: 6,
            }
        },
        '3': {
    
    
            name: '平行步枪',
            shake: {
    
    
                speed: 100,
                count: 5,
                strength: 5,
            }
        },
        '4': {
    
    
            name: '30-30',
        },
        '5': {
    
    
            name: 'CAR (重型弹药)',
            shake: {
    
    
                speed: 64.5,
                count: 10,
                strength: 7,
            }
        }
    },
    '3': {
    
      # 能量弹药武器
        '1': {
    
    
            name: 'L-STAR能量机枪',
            shake: {
    
    
                speed: 100,
                count: 10,
                strength: 5,
            }
        },
        '2': {
    
    
            name: '三重式狙击枪',
        },
        '3': {
    
    
            name: '电能冲锋枪',
            shake: {
    
    
                speed: 83.3,
                count: 10,
                strength: 7,
            }
        },
        '4': {
    
    
            name: '专注轻机枪',
            shake: {
    
    
                speed: 100,
                count: 10,
                strength: 7,
            }
        },
        '5': {
    
    
            name: '哈沃克步枪',
            shake: {
    
    
                speed: 100,
                count: 8,
                strength: 6,
            }
        },
    },
    '4': {
    
      # 狙击弹药武器
        '1': {
    
    
            name: '哨兵狙击步枪',
        },
        '2': {
    
    
            name: '充能步枪',
        },
        '3': {
    
    
            name: '辅助手枪',
        },
        '4': {
    
    
            name: '长弓',
        },
    },
    '5': {
    
      # 霰弹弹药武器
        '1': {
    
    
            name: '和平捍卫者霰弹枪',
        },
        '2': {
    
    
            name: '莫桑比克',
        },
        '3': {
    
    
            name: 'EVA-8',
        },
    },
    '6': {
    
      # 空投武器
        '1': {
    
    
            name: '克雷贝尔狙击枪',
        },
        '2': {
    
    
            name: '敖犬霰弹枪',
        },
        '3': {
    
    
            name: '波塞克',
        },
        '4': {
    
    
            name: '暴走',
            shake: {
    
    
                speed: 200,
                count: 8,
                strength: 2,
            }
        },
    }
}

kit de herramientas.py

import time

import mss  # pip install mss
import ctypes

from ctypes import CDLL

import cfg
from cfg import detect, weapon

# 全局 dll
user32 = ctypes.windll.user32
gdi32 = ctypes.windll.gdi32

try:
    driver = CDLL(r'mouse.device.lgs.dll')  # 在Python的string前面加上‘r’, 是为了告诉编译器这个string是个raw string(原始字符串),不要转义backslash(反斜杠) '\'
    ok = driver.device_open() == 1
    if not ok:
        print('初始化失败, 未安装lgs/ghub驱动')
except FileNotFoundError:
    print('初始化失败, 缺少文件')


class Mouse:

    @staticmethod
    def point():
        return user32.GetCursorPos()

    @staticmethod
    def move(x, y, absolute=False):
        if ok:
            mx, my = x, y
            if absolute:
                ox, oy = user32.GetCursorPos()
                mx = x - ox
                my = y - oy
            driver.moveR(mx, my, True)

    @staticmethod
    def moveHumanoid(x, y, absolute=False):
        """
        仿真移动(还没做好)
        """
        if ok:
            ox, oy = user32.GetCursorPos()  # 原鼠标位置
            mx, my = x, y  # 相对移动距离
            if absolute:
                mx = x - ox
                my = y - oy
            tx, ty = ox + mx, oy + my
            print(f'({
      
      ox},{
      
      oy}), ({
      
      tx},{
      
      ty}), x:{
      
      mx},y:{
      
      my}')
            # 以绝对位置方式移动(防止相对位置丢失精度)
            adx, ady = abs(mx), abs(my)
            if adx <= ady:
                # 水平方向移动的距离短
                for i in range(1, adx):
                    ix = i if mx > 0 else -i
                    temp = int(ady / adx * abs(ix))
                    iy = temp if my > 0 else -temp
                    Mouse.move(ox + ix, oy + iy, absolute=True)
                    # time.sleep(0.001)
            else:
                # 垂直方向移动的距离短
                for i in range(1, ady):
                    iy = i if my > 0 else -i
                    temp = int(adx / ady * abs(iy))
                    ix = temp if mx > 0 else -temp
                    Mouse.move(ox + ix, oy + iy, absolute=True)
                    # time.sleep(0.001)

    @staticmethod
    def down(code):
        if ok:
            driver.mouse_down(code)

    @staticmethod
    def up(code):
        if ok:
            driver.mouse_up(code)

    @staticmethod
    def click(code):
        """
        :param code: 1:左键, 2:中键, 3:右键, 4:侧下键, 5:侧上键, 6:DPI键
        :return:
        """
        if ok:
            driver.mouse_down(code)
            driver.mouse_up(code)


class Keyboard:

    @staticmethod
    def press(code):
        if ok:
            driver.key_down(code)

    @staticmethod
    def release(code):
        if ok:
            driver.key_up(code)

    @staticmethod
    def click(code):
        """
        键盘按键函数中,传入的参数采用的是键盘按键对应的键码
        :param code: 'a'-'z':A键-Z键, '0'-'9':0-9, 其他的没猜出来
        :return:
        """
        if ok:
            driver.key_down(code)
            driver.key_up(code)


class Monitor:
    """
    显示器
    """
    sct = mss.mss()

    @staticmethod
    def grab(region):
        """
        region: tuple, (left, top, width, height)
        pip install mss
        """
        left, top, width, height = region
        return Monitor.sct.grab(monitor={
    
    'left': left, 'top': top, 'width': width, 'height': height})

    @staticmethod
    def pixel(x, y):
        """
        效率很低且不稳定, 单点检测都要耗时1-10ms
        获取颜色, COLORREF 格式, 0x00FFFFFF
        结果是int,
        可以通过 print(hex(color)) 查看十六进制值
        可以通过 print(color == 0x00FFFFFF) 进行颜色判断
        """
        hdc = user32.GetDC(None)
        return gdi32.GetPixel(hdc, x, y)

    class Resolution:
        """
        分辨率
        """

        @staticmethod
        def display():
            """
            显示分辨率
            """
            w = user32.GetSystemMetrics(0)
            h = user32.GetSystemMetrics(1)
            return w, h

        @staticmethod
        def virtual():
            """
            多屏幕组合的虚拟显示器分辨率
            """
            w = user32.GetSystemMetrics(78)
            h = user32.GetSystemMetrics(79)
            return w, h

        @staticmethod
        def physical():
            """
            物理分辨率
            """
            hdc = user32.GetDC(None)
            w = gdi32.GetDeviceCaps(hdc, 118)
            h = gdi32.GetDeviceCaps(hdc, 117)
            return w, h


class Game:
    """
    游戏工具
    """

    @staticmethod
    def game():
        """
        是否在游戏内(顶层窗口是游戏,且正在进行一局游戏,且游戏界面上有血条)
        """
        # 先判断是否是游戏窗口
        hwnd = user32.GetForegroundWindow()
        length = user32.GetWindowTextLengthW(hwnd)
        buffer = ctypes.create_unicode_buffer(length + 1)
        user32.GetWindowTextW(hwnd, buffer, length + 1)
        if 'Apex Legends' != buffer.value:
            return False
        # 是在游戏中, 再判断下是否有血条和生存物品包
        w, h = Monitor.Resolution.display()
        data = detect.get(f'{
      
      w}:{
      
      h}').get(cfg.game)
        for item in data:
            x, y = item.get(cfg.point)
            if Monitor.pixel(x, y) != item.get(cfg.color):
                return False
        return True

    @staticmethod
    def index():
        """
        武器索引和子弹类型索引
        :return: 武器位索引, 1:1号位, 2:2号位, None:无武器, 拳头(这个暂时无法判断)
                 子弹类型索引, 1:轻型, 2:重型, 3:能量, 4:狙击, 5:霰弹, 6:空投, None:无武器
        """
        w, h = Monitor.Resolution.display()
        data = detect.get(f'{
      
      w}:{
      
      h}').get(cfg.pack)
        x, y = data.get(cfg.point)
        color = Monitor.pixel(x, y)
        if data.get(cfg.color) == color:
            return None, None
        else:
            bullet = data.get(hex(color))
            return (1, bullet) if color == Monitor.pixel(x, y + 1) else (2, bullet)

    @staticmethod
    def weapon(index, bullet):
        """
        通过武器位和子弹类型识别武器, 参考:config.detect.name
        :param index: 武器位, 1:1号位, 2:2号位
        :param bullet: 子弹类型, 1:轻型, 2:重型, 3:能量, 4:狙击, 5:霰弹, 6:空投
        :return:
        """
        w, h = Monitor.Resolution.display()
        data = detect.get(f'{
      
      w}:{
      
      h}').get(cfg.name)
        color = data.get(cfg.color)
        if index == 1:
            lst = data.get(str(index)).get(str(bullet))
            for i in range(len(lst)):
                x, y = lst[i]
                if color == Monitor.pixel(x, y):
                    return i + 1
        elif index == 2:
            differ = data.get(str(index)).get(cfg.differ)
            lst = data.get(str(1)).get(str(bullet))
            for i in range(len(lst)):
                x, y = lst[i]
                if color == Monitor.pixel(x + differ, y):
                    return i + 1
        return None

    @staticmethod
    def mode():
        """
        武器模式
        :return:  1:全自动, 2:半自动, None:其他
        """
        w, h = Monitor.Resolution.display()
        data = detect.get(f'{
      
      w}:{
      
      h}').get(cfg.mode)
        color = data.get(cfg.color)
        x, y = data.get('1')
        if color == Monitor.pixel(x, y):
            return 1
        x, y = data.get('2')
        if color == Monitor.pixel(x, y):
            return 2
        return None

    @staticmethod
    def detect(data):
        """
        决策是否需要压枪, 向信号量写数据
        """
        if data[cfg.switch] is False:
            print('开关已关闭')
            return
        t1 = time.perf_counter_ns()
        if Game.game() is False:
            print('不在游戏中')
            data[cfg.shake] = None
            return
        index, bullet = Game.index()
        if (index is None) | (bullet is None):
            print('没有武器')
            data[cfg.shake] = None
            return
        if Game.mode() is None:
            print('不是自动/半自动武器')
            data[cfg.shake] = None
            return
        arms = Game.weapon(index, bullet)
        if arms is None:
            print('识别武器失败')
            data[cfg.shake] = None
            return
        # 检测通过, 需要压枪
        gun = weapon.get(str(bullet)).get(str(arms))
        data[cfg.shake] = gun.get(cfg.shake)  # 记录当前武器抖动参数
        t2 = time.perf_counter_ns()
        print(f'耗时:{
      
      t2-t1}ns, 约{
      
      (t2-t1)//1000000}ms, {
      
      gun.get(cfg.name)}')

apex.py

import multiprocessing
import time
from multiprocessing import Process

import pynput  # conda install pynput

import toolkit


end = 'end'
fire = 'fire'
shake = 'shake'
speed = 'speed'
count = 'count'
switch = 'switch'
strength = 'strength'
init = {
    
    
    end: False,  # 退出标记, End 键按下后改为 True, 其他进程线程在感知到变更后结束自身
    switch: True,  # 开关
    fire: False,  # 开火状态
    shake: None,  # 抖枪参数
}


def listener(data):

    def down(x, y, button, pressed):
        if data[end]:
            return False  # 结束监听线程
        if button == pynput.mouse.Button.right:
            if pressed:
                toolkit.Game.detect(data)
        elif button == pynput.mouse.Button.left:
            data[fire] = pressed

    mouse = pynput.mouse.Listener(on_click=down)
    mouse.start()

    def release(key):
        if key == pynput.keyboard.Key.end:
            # 结束程序
            data[end] = True
            return False
        elif key == pynput.keyboard.Key.home:
            # 压枪开关
            data[switch] = not data[switch]
        elif key == pynput.keyboard.Key.esc:
            toolkit.Game.detect(data)
        elif key == pynput.keyboard.Key.tab:
            toolkit.Game.detect(data)
        elif key == pynput.keyboard.KeyCode.from_char('1'):
            toolkit.Game.detect(data)
        elif key == pynput.keyboard.KeyCode.from_char('2'):
            toolkit.Game.detect(data)
        elif key == pynput.keyboard.KeyCode.from_char('3'):
            toolkit.Game.detect(data)
        elif key == pynput.keyboard.KeyCode.from_char('e'):
            toolkit.Game.detect(data)
        elif key == pynput.keyboard.KeyCode.from_char('v'):
            toolkit.Game.detect(data)

    keyboard = pynput.keyboard.Listener(on_release=release)
    keyboard.start()
    keyboard.join()  # 卡住监听进程, 当键盘线程结束后, 监听进程才能结束


def suppress(data):
    while True:
        if data[end]:
            break
        if data[switch] is False:
            continue
        if data[fire] & (data[shake] is not None):
            # 301 大约75ms一发子弹
            total = 0  # 总计时 ms
            delay = 1  # 延迟 ms
            pixel = 4  # 抖动像素
            while True:
                if not data[fire]:
                    break
                # 下压
                t = time.perf_counter_ns()
                if total < data[shake][speed] * data[shake][count]:
                    toolkit.Mouse.move(0, data[shake][strength])
                    time.sleep(delay / 1000)
                    total += delay
                else:
                    toolkit.Mouse.move(0, 1)
                    time.sleep(delay / 1000)
                    total += delay
                # 抖枪
                toolkit.Mouse.move(pixel, 0)
                time.sleep(delay / 1000)
                total += delay
                toolkit.Mouse.move(-pixel, 0)
                time.sleep(delay / 1000)
                total += delay
                total += (time.perf_counter_ns() - t) // 1000 // 1000


if __name__ == '__main__':
    multiprocessing.freeze_support()  # windows 平台使用 multiprocessing 必须在 main 中第一行写这个
    manager = multiprocessing.Manager()
    data = manager.dict()  # 创建进程安全的共享变量
    data.update(init)  # 将初始数据导入到共享变量
    # 将键鼠监听和压枪放到单独进程中跑
    p1 = Process(target=listener, args=(data,))  # 监听进程
    p2 = Process(target=suppress, args=(data,))  # 压枪进程
    p1.start()
    p2.start()
    p1.join()  # 卡住主进程, 当进程 listener 结束后, 主进程才会结束

La tercera etapa realiza el abandono de la técnica gun shake y el método de compensación de retroceso convencional (en optimización)

Se ajustaron algunas pistolas, con o sin turbo, con o sin gatillos dobles, con parámetros de pistola de presión usando parámetros de pistola de presión, otras usando parámetros de agitación de pistola, pistola de presión y agitación de pistola coexisten

La configuración de mi mouse en el juego es así, asegúrese de que los ADS de cada alcance sean los mismos, el DPI del mouse es 3200

El efecto final es que 20 metros es muy estable, 30 metros estarán cerca, 50 metros no es suficiente, existe la posibilidad de ser derribado en un transbordador, y luego no tendrá sentido.
inserte la descripción de la imagen aquí

Cómo ajustar los parámetros de la pistola

Creo que el punto más importante al ajustar los parámetros es calcular primero la velocidad de disparo correcta de las balas (tiempo promedio por bala).

Resumo el método de prueba. En primer lugar, cada bala suele tardar entre 50 y 150 milisegundos. Supongamos que son 100. Para ver cuántas balas hay, copie la mayor cantidad posible de datos de presión de la pistola. Por ejemplo

R-301 Esta pistola, más expansión dorada, 28 balas, luego prepare primero los siguientes datos iniciales, los tres parámetros son, el valor del movimiento horizontal del mouse/el valor del movimiento vertical/tiempo de suspensión después de moverse, por supuesto, puede también con otros parámetros

Primero, configure el valor de movimiento del mouse correspondiente a la última bala en 10000, para ver si el mouse tiene un gran desplazamiento cuando se dispara la bala, y luego ajuste los últimos 100 hasta que coincida, y luego puede comenzar a ajustar el mouse parámetros

[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],  #
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],  #
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],  #
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],  #
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],  #
[0, 0, 100],
[0, 0, 100],
[10000, 0, 100],

Al ajustar los parámetros del mouse, debe ajustarlos uno por uno de arriba a abajo, porque un cambio en la parte superior tiene un gran impacto en la parte inferior y es probable que cause el tono blanco debajo.

Por ejemplo, al ajustar la supresión vertical, el espejo 1x apunta a esta barra a 30 metros y se esfuerza por estar básicamente en la barra, la vertical está bien y la horizontal es la misma.

inserte la descripción de la imagen aquí

También puede usar la herramienta de grabación de video para grabar parte del área central de la pantalla y luego reproducirla a 0.1 veces la velocidad y verificar cuidadosamente si la fuerza de supresión es adecuada

El efecto final es que no es muy estable, el rendimiento del espejo 123x no es consistente y la desviación del espejo 3x es la más grande. ¿Es difícil hacer un conjunto de parámetros para cada espejo?

prueba en el juego

En general, el rendimiento estará bien, el paralelo está bien, los demás están en la media, todavía está lejos de una línea

Problemas existentes

  • Usando el método de juicio de selección de color, la selección de color de un solo punto toma de 1 a 10 ms, y el rendimiento es insuficiente
  • La detección del nombre del arma utiliza el método transversal de complejidad temporal O(n). En el caso de la baja eficiencia del método de juicio de selección de color, el rendimiento no es lo suficientemente excelente y estable, y se espera que logre O(1 )
  • Es imposible juzgar si hay un arma (hay un arma pero uso mi puño, lo que puede causar que la pistola de presión se dispare por error)
  • Se congelará después de ejecutarse por un tiempo. Es muy estable. Se congela cada dos segundos en promedio. Todo el mouse, el teclado y las pantallas de la computadora se congelan al mismo tiempo, lo que afecta seriamente el juego. No sé por qué.
  • No es posible simular el efecto de hacer clic en el botón izquierdo al presionar el botón izquierdo, por lo que es temporalmente imposible realizar la función de cambiar una pistola de un solo disparo a una pistola de ráfaga.

Código detallado

cfg.py

mode = 'mode'
name = 'name'
game = 'game'
data = 'data'
pack = 'pack'
color = 'color'
point = 'point'
index = 'index'
shake = 'shake'
speed = 'speed'
count = 'count'
armed = 'armed'
empty = 'empty'
switch = 'switch'
bullet = 'bullet'  # 子弹
differ = 'differ'
turbo = 'turbo'
trigger = 'trigger'
restrain = 'restrain'
strength = 'strength'
positive = 'positive'  # 肯定的
negative = 'negative'  # 否定的

# 检测数据
detect = {
    
    
    "3440:1440": {
    
    
        game: [  # 判断是否在游戏中
            {
    
    
                point: (236, 1344),  # 点的坐标, 血条左上角
                color: 0x00FFFFFF  # 点的颜色, 255, 255, 255
            },
            {
    
    
                point: (2692, 1372),  # 生存物品右下角
                color: 0x959595  # 149, 149, 149
            }
        ],
        pack: {
    
      # 背包状态, 有无武器, 选择的武器
            point: (2900, 1372),  # 两把武器时, 1号武器上面边框分界线的上半部分, y+1 就是1号武器上面边框分界线的下半部分
            color: 0x808080,  # 无武器时, 灰色, 128, 128, 128
            '0x447bb4': 1,  # 轻型弹药武器, 子弹类型: 1/2/3/4/5/6/None(无武器)
            '0x839b54': 2,  # 重型弹药武器
            '0x3da084': 3,  # 能量弹药武器
            '0xce5f6e': 4,  # 狙击弹药武器
            '0xf339b': 5,  # 霰弹枪弹药武器
            '0x5302ff': 6,  # 空投武器
        },
        mode: {
    
      # 武器模式, 全自动/半自动/单发/其他
            color: 0x00FFFFFF,
            '1': (3151, 1347),  # 全自动
            '2': (3171, 1351),  # 半自动
        },
        armed: {
    
      # 是否持有武器(比如有武器但用拳头就是未持有武器)

        },
        empty: {
    
      # 是否空弹夹(武器里子弹数为0)
            color: 0x00FFFFFF,
            '1': (3204, 1306),  # 十位数, 该点白色即非0, 非0则一定不空
            '2': (3229, 1294),  # 个位数, 该点白色即为0, 十位为0且个位为0为空
        },
        name: {
    
      # 武器名称判断
            color: 0x00FFFFFF,
            '1': {
    
      # 1号武器
                '1': [  # 轻型弹药武器
                    (2959, 1386),  # 1: RE-45 自动手枪
                    (2970, 1385),  # 2: 转换者冲锋枪
                    (2972, 1386),  # 3: R-301 卡宾枪
                    (2976, 1386),  # 4: R-99 冲锋枪
                    (2980, 1386),  # 5: P2020 手枪
                    (2980, 1384),  # 6: 喷火轻机枪
                    (2987, 1387),  # 7: G7 侦查枪
                    (3015, 1386),  # 8: CAR (轻型弹药)
                ],
                '2': [  # 重型弹药武器
                    (2957, 1385),  # 1: 赫姆洛克突击步枪
                    (2982, 1385),  # 2: 猎兽冲锋枪
                    (2990, 1393),  # 3: 平行步枪
                    (3004, 1386),  # 4: 30-30
                    (3015, 1386),  # 5: CAR (重型弹药)
                ],
                '3': [  # 能量弹药武器
                    (2955, 1386),  # 1: L-STAR 能量机枪
                    (2970, 1384),  # 2: 三重式狙击枪
                    (2981, 1385),  # 3: 电能冲锋枪
                    (2986, 1384),  # 4: 专注轻机枪
                    (2980, 1384),  # 5: 哈沃克步枪
                ],
                '4': [  # 狙击弹药武器
                    (2969, 1395),  # 1: 哨兵狙击步枪
                    (2999, 1382),  # 2: 充能步枪
                    (2992, 1385),  # 3: 辅助手枪
                    (3016, 1383),  # 4: 长弓
                ],
                '5': [  # 霰弹枪弹药武器
                    (2957, 1384),  # 1: 和平捍卫者霰弹枪
                    (2995, 1382),  # 2: 莫桑比克
                    (3005, 1386),  # 3: EVA-8
                ],
                '6': [  # 空投武器
                    (2958, 1384),  # 1: 克雷贝尔狙击枪
                    (2959, 1384),  # 2: 手感卓越的刀刃
                    (2983, 1384),  # 3: 敖犬霰弹枪
                    (3003, 1383),  # 4: 波塞克
                    (3014, 1383),  # 5: 暴走
                ]
            },
            '2': {
    
    
                differ: 195  # 直接用1的坐标, 横坐标右移195就可以了
            }
        },
        turbo: {
    
      # 涡轮
            color: 0x00FFFFFF,
            '3': {
    
    
                differ: 2,  # 有涡轮和没涡轮的索引偏移
                '4': (3072, 1358),  # 专注轻机枪 涡轮检测位置
                '5': (3034, 1358),  # 哈沃克步枪 涡轮检测位置
            }
        },
        trigger: {
    
      # 双发扳机
            color: 0x00FFFFFF,
            '1': {
    
    
                differ: 2,
                '7': (3072, 1358),  # G7 侦查枪 双发扳机检测位置
            },
            '5': {
    
    
                differ: 1,
                '3': (3034, 1358),  # EVA-8 双发扳机检测位置
            }
        }
    },
    "2560:1440": {
    
    

    },
    "2560:1080": {
    
    

    },
    "1920:1080": {
    
    

    }
}

# 武器数据
weapon = {
    
    
    '1': {
    
      # 轻型弹药武器
        '1': {
    
    
            name: 'RE-45 自动手枪',  # 全程往右飘
            shake: {
    
    
                speed: 80,
                count: 10,
                strength: 5,
            },
            restrain: [
                [1, -2, 10, 80],  #
                [1, -2, 10, 80],
                [1, -2, 10, 80],
                [1, -4, 10, 80],
                [1, -6, 10, 80],
                [1, -7, 8, 80],  #
                [1, -7, 8, 80],
                [1, -7, 8, 80],
                [1, -7, 8, 80],
                [1, -7, 8, 80],
                [1, -1, 5, 80],  #
                [1, -1, 5, 80],
                [1, -1, 5, 80],
                [1, -1, 5, 80],
                [1, -1, 5, 80],
                [1, -1, 5, 80],  #
                [1, -1, 3, 80],
                [1, -1, 3, 80],
                [1, -1, 3, 80],
                [1, -1, 3, 80],
                [1, -1, 3, 80],  #
                [1, -2, 3, 80],
                [1, -2, 3, 80],
                [1, -2, 3, 80],
                [1, -2, 3, 80],
                [1, -5, 3, 80],  #
                [1, -5, 3, 80],
                [1, -10, 3, 80],
                [1, -10, 3, 80],
                [1, -10, 3, 80],
            ]
        },
        '2': {
    
    
            name: '转换者冲锋枪',
            shake: {
    
    
                speed: 100,
                count: 10,
                strength: 7,
            },
            restrain: [
                [1, 0, 15, 94],
                [1, 0, 15, 94],
                [1, 0, 15, 94],
                [1, 0, 15, 94],
                [1, 0, 15, 94],  #
                [1, 0, 15, 94],
                [1, 0, 15, 94],
                [1, 0, 10, 94],
                [1, 0, 10, 94],
                [1, 0, 10, 94],  #
                [1, -5, 5, 94],
                [1, -5, 5, 94],
                [1, -5, 5, 94],
                [1, 0, 5, 94],
                [1, 0, 5, 94],  #
                [1, 0, 5, 94],
                [1, 5, 5, 94],
                [1, 5, 5, 94],
                [1, 5, 5, 94],
                [1, 0, 5, 94],  #
                [1, 0, 5, 94],
                [1, 0, 5, 94],
                [1, 0, 5, 94],
                [1, 0, 5, 94],
                [1, 0, 5, 94],  #
                [1, 0, 5, 94],
                [1, 0, 0, 94],
            ]
        },
        '3': {
    
    
            name: 'R-301 卡宾枪',
            shake: {
    
    
                speed: 64,  # 74ms打一发子弹
                count: 6,  # 压制前6发
                strength: 5,  # 压制的力度(下移的像素)
            },
            restrain: [
                [1, -5, 10, 70],
                [1, 0, 10, 70],
                [1, -5, 10, 70],
                [1, -2, 10, 70],
                [1, 0, 10, 70],  #
                [1, 0, 5, 70],
                [1, 0, 0, 70],
                [1, -5, 0, 70],
                [1, -5, 5, 70],
                [1, 0, 0, 70],  #
                [1, 0, 0, 70],
                [1, 5, 10, 70],
                [1, 5, 5, 70],
                [1, 5, 0, 70],
                [1, 5, 0, 70],  #
                [1, 0, 0, 70],
                [1, 5, 0, 70],
                [1, 5, 10, 70],
                [1, 0, 10, 70],
                [1, -5, 0, 70],  #
                [1, -5, 0, 70],
                [1, -5, 0, 70],
                [1, -5, 0, 70],
                [1, -5, 0, 70],
                [1, 0, 0, 70],  #
                [1, 0, 0, 70],
                [1, 0, 0, 70],
                [1, 0, 0, 64],
            ]
        },
        '4': {
    
    
            name: 'R-99 冲锋枪',
            shake: {
    
    
                speed: 55.5,
                count: 13,
                strength: 8,
            },
            restrain: [
                [1, 0, 10, 48],
                [1, 0, 10, 48],
                [1, 0, 10, 48],
                [1, -5, 10, 48],
                [1, -5, 10, 48],  #
                [1, -5, 10, 48],
                [1, -5, 10, 48],
                [1, 0, 10, 48],
                [1, 0, 10, 48],
                [1, 0, 10, 48],  #
                [1, 5, 10, 48],
                [1, 5, 10, 48],
                [1, 5, 10, 48],
                [1, 0, 10, 48],
                [1, 0, 0, 48],  #
                [1, -5, 0, 48],
                [1, -10, 0, 48],
                [1, 0, 0, 48],
                [1, 0, 0, 48],
                [1, 5, 5, 48],  #
                [1, 10, 5, 48],
                [1, 10, 5, 48],
                [1, 5, 0, 48],
                [1, 0, 0, 48],
                [1, -5, 0, 48],  #
                [1, -5, 0, 48],
                [1, -5, 0, 48],
            ]
        },
        '5': {
    
    
            name: 'P2020 手枪',
            restrain: [
                [2, 1, 100],
            ]
        },
        '6': {
    
    
            name: '喷火轻机枪',
            shake: {
    
    
                speed: 110,
                count: 8,
                strength: 5,
            },
            restrain: [
                [1, 0, 20, 100],
                [1, 5, 15, 100],
                [1, 5, 15, 100],
                [1, 5, 15, 100],
                [1, 5, 15, 100],  #
                [1, 5, 15, 100],
                [1, -5, 10, 100],
                [1, -5, 0, 100],
                [1, -5, 0, 100],
                [1, -5, 0, 100],  #
                [1, 0, 0, 100],
                [1, 0, 5, 100],
                [1, 0, 5, 100],
                [1, 5, 5, 100],
                [1, 10, 5, 100],  #
                [1, 10, 5, 100],
                [1, 5, 5, 100],
                [1, 0, 5, 100],
                [1, 0, 5, 100],
                [1, 0, 5, 100],  # 20
                [1, 0, 0, 100],
                [1, 0, 0, 100],
                [1, 0, 0, 100],
                [1, 0, 0, 100],
                [1, -5, 5, 100],  #
                [1, -5, 5, 100],
                [1, -5, 5, 100],
                [1, -5, 5, 100],
                [1, 0, 5, 100],
                [1, 0, 5, 100],  #
                [1, 0, 5, 100],
                [1, 0, 5, 100],
                [1, 0, 5, 100],
                [1, 0, 5, 100],
                [1, 0, 5, 100],  #
                [1, 0, 5, 100],
                [1, 0, 5, 100],
                [1, 0, 5, 100],
                [1, 0, 5, 100],
                [1, 0, 5, 100],  #
                [1, 0, 5, 100],
                [1, 0, 5, 100],
                [1, 0, 5, 100],
                [1, 0, 5, 100],
                [1, 0, 5, 100],  #
                [1, 0, 5, 100],
                [1, 0, 5, 100],
                [1, 0, 5, 100],
                [1, 0, 5, 100],
                [1, 0, 0, 100],  #
            ]
        },
        '7': {
    
    
            name: 'G7 侦查枪',
        },
        '8': {
    
    
            name: 'CAR (轻型弹药)',
            shake: {
    
    
                speed: 64.5,
                count: 10,
                strength: 7,
            },
            restrain: [
                [1, 0, 10, 58],  #
                [1, 3, 10, 58],
                [1, 3, 10, 58],
                [1, 3, 10, 58],
                [1, 3, 10, 58],
                [1, 3, 10, 58],  #
                [1, 3, 10, 58],
                [1, 3, 10, 58],
                [1, -5, 10, 58],
                [1, -5, 10, 58],
                [1, -5, 5, 58],  #
                [1, -5, 10, 58],
                [1, -5, 0, 58],
                [1, 0, 0, 58],
                [1, 5, 0, 58],
                [1, 5, 3, 58],  #
                [1, 5, 3, 58],
                [1, -5, 3, 58],
                [1, -5, 3, 58],
                [1, -5, 3, 58],
                [1, 0, 0, 58],  #
                [1, 0, 0, 58],
                [1, 0, 0, 58],
                [1, 0, 3, 58],
                [1, 0, 3, 58],
                [1, 0, 3, 58],  #
                [1, 0, 3, 58],
            ]
        },
        '9': {
    
    
            name: 'G7 侦查枪 (双发扳机)',
            restrain: [
                [1, 0, 5, 20]
            ]
        },
    },
    '2': {
    
      # 重型弹药武器
        '1': {
    
    
            name: '赫姆洛克突击步枪',
            shake: {
    
    
                speed: 50,
                count: 3,
                strength: 6,
            }
        },
        '2': {
    
    
            name: '猎兽冲锋枪',
            shake: {
    
    
                speed: 50,
                count: 5,
                strength: 6,
            }
        },
        '3': {
    
    
            name: '平行步枪',
            shake: {
    
    
                speed: 100,
                count: 5,
                strength: 5,
            },
            restrain: [
                [1, 0, 10, 100],  #
                [1, 5, 10, 100],
                [1, 5, 10, 100],
                [1, 5, 10, 100],
                [1, 5, 10, 100],
                [1, -5, 10, 100],  #
                [1, -5, 0, 100],
                [1, -5, 0, 100],
                [1, -5, 0, 100],
                [1, 0, 5, 100],
                [1, 5, 5, 100],  #
                [1, 5, 5, 100],
                [1, 5, 0, 100],
                [1, 5, 0, 100],
                [1, 0, 0, 100],
                [1, 5, 5, 100],  #
                [1, 5, 5, 100],
                [1, 5, 5, 100],
                [1, 0, 0, 100],
                [1, 0, 0, 100],
                [1, -5, 5, 100],  #
                [1, -5, 5, 100],
                [1, -5, 5, 100],
                [1, -0, 5, 100],
                [1, 5, 5, 100],
                [1, 5, 5, 100],  #
                [1, 5, 5, 100],
                [1, -5, -5, 100],
                [1, -5, 5, 100],
                [1, -5, 5, 100],
            ]
        },
        '4': {
    
    
            name: '30-30',
        },
        '5': {
    
    
            name: 'CAR (重型弹药)',
            shake: {
    
    
                speed: 58,
                count: 10,
                strength: 7,
            },
            restrain: [
                [1, 0, 10, 58],  #
                [1, 3, 10, 58],
                [1, 3, 10, 58],
                [1, 3, 10, 58],
                [1, 3, 10, 58],
                [1, 3, 10, 58],  #
                [1, 3, 10, 58],
                [1, 3, 10, 58],
                [1, -5, 10, 58],
                [1, -5, 10, 58],
                [1, -5, 5, 58],  #
                [1, -5, 10, 58],
                [1, -5, 0, 58],
                [1, 0, 0, 58],
                [1, 5, 0, 58],
                [1, 5, 3, 58],  #
                [1, 5, 3, 58],
                [1, -5, 3, 58],
                [1, -5, 3, 58],
                [1, -5, 3, 58],
                [1, 0, 0, 58],  #
                [1, 0, 0, 58],
                [1, 0, 0, 58],
                [1, 0, 3, 58],
                [1, 0, 3, 58],
                [1, 0, 3, 58],  #
                [1, 0, 3, 58],
            ]
        }
    },
    '3': {
    
      # 能量弹药武器
        '1': {
    
    
            name: 'L-STAR 能量机枪',
            shake: {
    
    
                speed: 100,
                count: 10,
                strength: 5,
            }
        },
        '2': {
    
    
            name: '三重式狙击枪',
        },
        '3': {
    
    
            name: '电能冲锋枪',
            shake: {
    
    
                speed: 83.3,
                count: 10,
                strength: 7,
            },
            restrain: [
                [1, -5, 15, 80],
                [1, 0, 15, 80],
                [1, 0, 15, 80],
                [1, 0, 15, 80],
                [1, 0, 15, 80],  #
                [1, -5, 10, 80],
                [1, -5, 10, 80],
                [1, -5, 10, 80],
                [1, 0, 10, 80],
                [1, 5, 10, 80],  #
                [1, 5, 5, 80],
                [1, 5, 5, 80],
                [1, 5, 5, 80],
                [1, 0, 5, 80],
                [1, 0, 5, 80],  #
                [1, 0, 5, 80],
                [1, 0, 0, 80],
                [1, 0, 0, 80],
                [1, 0, 0, 80],
                [1, 0, 0, 80],  #
                [1, 0, 0, 80],
                [1, 5, 0, 80],
                [1, 5, 0, 80],
                [1, 5, 0, 80],
                [1, 0, 0, 80],  #
                [1, 0, 0, 80],
            ]
        },
        '4': {
    
    
            name: '专注轻机枪',
            shake: {
    
    
                speed: 100,
                count: 10,
                strength: 7,
            }
        },
        '5': {
    
    
            name: '哈沃克步枪',
            shake: {
    
    
                speed: 100,
                count: 8,
                strength: 6,
            },
            restrain: [
                [1, 0, 0, 400],  # 延迟
                [1, -5, 10, 88],  # 1
                [1, -5, 15, 88],
                [1, 0, 15, 88],
                [1, 0, 15, 88],
                [1, 0, 15, 88],
                [1, 5, 10, 88],  #
                [1, 5, 10, 88],
                [1, 5, 10, 88],
                [1, 5, 10, 88],
                [1, -5, 5, 88],
                [1, -5, 0, 88],  # 1
                [1, -5, 0, 88],
                [1, -10, 0, 88],
                [1, -10, 0, 88],
                [1, -5, 0, 88],
                [1, 0, 5, 88],  #
                [1, 10, 5, 88],
                [1, 10, 5, 88],
                [1, 0, 0, 88],
                [1, 0, 0, 88],
                [1, 5, 10, 88],  # 1
                [1, 5, 10, 88],
                [1, 0, 10, 88],
                [1, 5, 10, 88],
                [1, 5, 10, 88],
                [1, 5, 10, 88],  #
                [1, 5, 5, 88],
                [1, 5, 5, 88],
                [1, 0, 5, 88],
                [1, 0, 0, 88],
                [1, 0, 0, 88],  # 1
                [1, 0, 0, 88],
                [1, 0, 5, 88],
                [1, 0, 5, 88],
                [1, 0, 5, 88],
                [1, 0, 5, 88],  #
            ]
        },
        '6': {
    
    
            name: '专注轻机枪 (涡轮)',
            shake: {
    
    
                speed: 100,
                count: 10,
                strength: 7,
            }
        },
        '7': {
    
    
            name: '哈沃克步枪 (涡轮)',
            shake: {
    
    
                speed: 100,
                count: 8,
                strength: 6,
            },
            restrain: [
                [1, -5, 10, 88],  # 1
                [1, -5, 15, 88],
                [1, 0, 15, 88],
                [1, 0, 15, 88],
                [1, 0, 15, 88],
                [1, 5, 10, 88],  #
                [1, 5, 10, 88],
                [1, 5, 10, 88],
                [1, 5, 10, 88],
                [1, -5, 5, 88],
                [1, -5, 0, 88],  # 1
                [1, -5, 0, 88],
                [1, -10, 0, 88],
                [1, -10, 0, 88],
                [1, -5, 0, 88],
                [1, 0, 5, 88],  #
                [1, 10, 5, 88],
                [1, 10, 5, 88],
                [1, 0, 0, 88],
                [1, 0, 0, 88],
                [1, 5, 10, 88],  # 1
                [1, 5, 10, 88],
                [1, 0, 10, 88],
                [1, 5, 10, 88],
                [1, 5, 10, 88],
                [1, 5, 10, 88],  #
                [1, 5, 5, 88],
                [1, 5, 5, 88],
                [1, 0, 5, 88],
                [1, 0, 0, 88],
                [1, 0, 0, 88],  # 1
                [1, 0, 0, 88],
                [1, 0, 5, 88],
                [1, 0, 5, 88],
                [1, 0, 5, 88],
                [1, 0, 5, 88],  #
            ]
        },
    },
    '4': {
    
      # 狙击弹药武器
        '1': {
    
    
            name: '哨兵狙击步枪',
        },
        '2': {
    
    
            name: '充能步枪',
        },
        '3': {
    
    
            name: '辅助手枪',
        },
        '4': {
    
    
            name: '长弓',
        },
    },
    '5': {
    
      # 霰弹弹药武器
        '1': {
    
    
            name: '和平捍卫者霰弹枪',
        },
        '2': {
    
    
            name: '莫桑比克',
        },
        '3': {
    
    
            name: 'EVA-8',
        },
        '4': {
    
    
            name: 'EVA-8 (双发扳机)',
        }
    },
    '6': {
    
      # 空投武器
        '1': {
    
    
            name: '克雷贝尔狙击枪',
        },
        '2': {
    
    
            name: '手感卓越的刀刃',
        },
        '3': {
    
    
            name: '敖犬霰弹枪',
        },
        '4': {
    
    
            name: '波塞克',
        },
        '5': {
    
    
            name: '暴走',
            shake: {
    
    
                speed: 200,
                count: 8,
                strength: 2,
            }
        },
    }
}

kit de herramientas.py

import time

import mss  # pip install mss
import ctypes

from ctypes import CDLL

import cfg
from cfg import detect, weapon

# 全局 dll
user32 = ctypes.windll.user32
gdi32 = ctypes.windll.gdi32

try:
    driver = CDLL(r'mouse.device.lgs.dll')  # 在Python的string前面加上‘r’, 是为了告诉编译器这个string是个raw string(原始字符串),不要转义backslash(反斜杠) '\'
    ok = driver.device_open() == 1
    if not ok:
        print('初始化失败, 未安装lgs/ghub驱动')
except FileNotFoundError:
    print('初始化失败, 缺少文件')


class Mouse:

    @staticmethod
    def point():
        return user32.GetCursorPos()

    @staticmethod
    def move(x, y, absolute=False):
        if ok:
            if (x == 0) & (y == 0):
                return
            mx, my = x, y
            if absolute:
                ox, oy = user32.GetCursorPos()
                mx = x - ox
                my = y - oy
            driver.moveR(mx, my, True)

    @staticmethod
    def moveHumanoid(x, y, absolute=False):
        """
        仿真移动(还没做好)
        """
        if ok:
            ox, oy = user32.GetCursorPos()  # 原鼠标位置
            mx, my = x, y  # 相对移动距离
            if absolute:
                mx = x - ox
                my = y - oy
            tx, ty = ox + mx, oy + my
            print(f'({
      
      ox},{
      
      oy}), ({
      
      tx},{
      
      ty}), x:{
      
      mx},y:{
      
      my}')
            # 以绝对位置方式移动(防止相对位置丢失精度)
            adx, ady = abs(mx), abs(my)
            if adx <= ady:
                # 水平方向移动的距离短
                for i in range(1, adx):
                    ix = i if mx > 0 else -i
                    temp = int(ady / adx * abs(ix))
                    iy = temp if my > 0 else -temp
                    Mouse.move(ox + ix, oy + iy, absolute=True)
                    # time.sleep(0.001)
            else:
                # 垂直方向移动的距离短
                for i in range(1, ady):
                    iy = i if my > 0 else -i
                    temp = int(adx / ady * abs(iy))
                    ix = temp if mx > 0 else -temp
                    Mouse.move(ox + ix, oy + iy, absolute=True)
                    # time.sleep(0.001)

    @staticmethod
    def down(code):
        if ok:
            driver.mouse_down(code)

    @staticmethod
    def up(code):
        if ok:
            driver.mouse_up(code)

    @staticmethod
    def click(code):
        """
        :param code: 1:左键, 2:中键, 3:右键, 4:侧下键, 5:侧上键, 6:DPI键
        :return:
        """
        if ok:
            driver.mouse_down(code)
            driver.mouse_up(code)


class Keyboard:

    @staticmethod
    def press(code):
        if ok:
            driver.key_down(code)

    @staticmethod
    def release(code):
        if ok:
            driver.key_up(code)

    @staticmethod
    def click(code):
        """
        键盘按键函数中,传入的参数采用的是键盘按键对应的键码
        :param code: 'a'-'z':A键-Z键, '0'-'9':0-9, 其他的没猜出来
        :return:
        """
        if ok:
            driver.key_down(code)
            driver.key_up(code)


class Monitor:
    """
    显示器
    """
    sct = mss.mss()

    @staticmethod
    def grab(region):
        """
        region: tuple, (left, top, width, height)
        pip install mss
        """
        left, top, width, height = region
        return Monitor.sct.grab(monitor={
    
    'left': left, 'top': top, 'width': width, 'height': height})

    @staticmethod
    def pixel(x, y):
        """
        效率很低且不稳定, 单点检测都要耗时1-10ms
        获取颜色, COLORREF 格式, 0x00FFFFFF
        结果是int,
        可以通过 print(hex(color)) 查看十六进制值
        可以通过 print(color == 0x00FFFFFF) 进行颜色判断
        """
        hdc = user32.GetDC(None)
        return gdi32.GetPixel(hdc, x, y)

    class Resolution:
        """
        分辨率
        """

        @staticmethod
        def display():
            """
            显示分辨率
            """
            w = user32.GetSystemMetrics(0)
            h = user32.GetSystemMetrics(1)
            return w, h

        @staticmethod
        def virtual():
            """
            多屏幕组合的虚拟显示器分辨率
            """
            w = user32.GetSystemMetrics(78)
            h = user32.GetSystemMetrics(79)
            return w, h

        @staticmethod
        def physical():
            """
            物理分辨率
            """
            hdc = user32.GetDC(None)
            w = gdi32.GetDeviceCaps(hdc, 118)
            h = gdi32.GetDeviceCaps(hdc, 117)
            return w, h


class Game:
    """
    游戏工具
    """

    @staticmethod
    def key():
        w, h = Monitor.Resolution.display()
        return f'{
      
      w}:{
      
      h}'

    @staticmethod
    def game():
        """
        是否游戏窗体在最前
        """
        # 先判断是否是游戏窗口
        hwnd = user32.GetForegroundWindow()
        length = user32.GetWindowTextLengthW(hwnd)
        buffer = ctypes.create_unicode_buffer(length + 1)
        user32.GetWindowTextW(hwnd, buffer, length + 1)
        if 'Apex Legends' != buffer.value:
            return False
        return True

    @staticmethod
    def play():
        """
        是否正在玩
        """
        # 是在游戏中, 再判断下是否有血条和生存物品包
        data = detect.get(Game.key()).get(cfg.game)
        for item in data:
            x, y = item.get(cfg.point)
            if Monitor.pixel(x, y) != item.get(cfg.color):
                return False
        return True

    @staticmethod
    def index():
        """
        武器索引和子弹类型索引
        :return: 武器位索引, 1:1号位, 2:2号位, None:无武器
                 子弹类型索引, 1:轻型, 2:重型, 3:能量, 4:狙击, 5:霰弹, 6:空投, None:无武器
        """
        data = detect.get(Game.key()).get(cfg.pack)
        x, y = data.get(cfg.point)
        color = Monitor.pixel(x, y)
        if data.get(cfg.color) == color:
            return None, None
        else:
            bi = data.get(hex(color))
            return (1, bi) if color == Monitor.pixel(x, y + 1) else (2, bi)

    @staticmethod
    def weapon(pi, bi):
        """
        通过武器位和子弹类型识别武器, 参考:config.detect.name
        :param pi: 武器位, 1:1号位, 2:2号位
        :param bi: 子弹类型, 1:轻型, 2:重型, 3:能量, 4:狙击, 5:霰弹, 6:空投
        :return:
        """
        data = detect.get(Game.key()).get(cfg.name)
        color = data.get(cfg.color)
        if pi == 1:
            lst = data.get(str(pi)).get(str(bi))
            for i in range(len(lst)):
                x, y = lst[i]
                if color == Monitor.pixel(x, y):
                    return i + 1
        elif pi == 2:
            differ = data.get(str(pi)).get(cfg.differ)
            lst = data.get(str(1)).get(str(bi))
            for i in range(len(lst)):
                x, y = lst[i]
                if color == Monitor.pixel(x + differ, y):
                    return i + 1
        return None

    @staticmethod
    def mode():
        """
        武器模式
        :return:  1:全自动, 2:半自动, None:其他
        """
        data = detect.get(Game.key()).get(cfg.mode)
        color = data.get(cfg.color)
        x, y = data.get('1')
        if color == Monitor.pixel(x, y):
            return 1
        x, y = data.get('2')
        if color == Monitor.pixel(x, y):
            return 2
        return None

    @staticmethod
    def armed():
        """
        是否持有武器
        """
        return True

    @staticmethod
    def empty():
        """
        是否空弹夹
        """
        data = detect.get(Game.key()).get(cfg.empty)
        color = data.get(cfg.color)
        x, y = data.get('1')
        if color == Monitor.pixel(x, y):
            return False
        x, y = data.get('2')
        return color == Monitor.pixel(x, y)

    @staticmethod
    def turbo(bi, wi):
        """
        判断是否有涡轮, 只有配置了检测涡轮的武器才会做取色判断
        :return: (False, None), (True, differ), 有涡轮的话, 额外返回涡轮索引偏移
        """
        data = detect.get(Game.key()).get(cfg.turbo)
        color = data.get(cfg.color)
        data = data.get(str(bi))
        if data is None:
            return False, None
        differ = data.get(cfg.differ)
        data = data.get(str(wi))
        if data is None:
            return False, None
        x, y = data
        result = color == Monitor.pixel(x, y)
        return (True, differ) if result else (False, None)

    @staticmethod
    def trigger(bi, wi):
        """
        判断是否有双发扳机, 只有配置了检测双发扳机的武器才会做取色判断
        :return: (False, None), (True, differ), 有双发扳机的话, 额外返回双发扳机索引偏移
        """
        data = detect.get(Game.key()).get(cfg.trigger)
        color = data.get(cfg.color)
        data = data.get(str(bi))
        if data is None:
            return False, None
        differ = data.get(cfg.differ)
        data = data.get(str(wi))
        if data is None:
            return False, None
        x, y = data
        result = color == Monitor.pixel(x, y)
        return (True, differ) if result else (False, None)

    @staticmethod
    def detect(data):
        """
        决策是否需要压枪, 向信号量写数据
        """
        t1 = time.perf_counter_ns()
        if data.get(cfg.switch) is False:
            t2 = time.perf_counter_ns()
            print(f'耗时: {
      
      t2 - t1}ns, 约{
      
      (t2 - t1) // 1000000}ms, 开关已关闭')
            return
        if Game.game() is False:
            data[cfg.shake] = None
            data[cfg.restrain] = None
            t2 = time.perf_counter_ns()
            print(f'耗时: {
      
      t2 - t1}ns, 约{
      
      (t2 - t1) // 1000000}ms, 不在游戏中')
            return
        if Game.play() is False:
            data[cfg.shake] = None
            data[cfg.restrain] = None
            t2 = time.perf_counter_ns()
            print(f'耗时: {
      
      t2 - t1}ns, 约{
      
      (t2 - t1) // 1000000}ms, 不在游戏中')
            return
        pi, bi = Game.index()
        if (pi is None) | (bi is None):
            data[cfg.shake] = None
            data[cfg.restrain] = None
            t2 = time.perf_counter_ns()
            print(f'耗时: {
      
      t2 - t1}ns, 约{
      
      (t2 - t1) // 1000000}ms, 没有武器')
            return
        # if Game.mode() is None:
        #     data[cfg.shake] = None
        #     data[cfg.restrain] = None
        #     t2 = time.perf_counter_ns()
        #     print(f'耗时: {t2 - t1}ns, 约{(t2 - t1) // 1000000}ms, 不是自动/半自动武器')
        #     return
        wi = Game.weapon(pi, bi)
        if wi is None:
            data[cfg.shake] = None
            data[cfg.restrain] = None
            t2 = time.perf_counter_ns()
            print(f'耗时: {
      
      t2 - t1}ns, 约{
      
      (t2 - t1) // 1000000}ms, 识别武器失败')
            return
        # 检测通过, 需要压枪
        # 检测涡轮
        result, differ = Game.turbo(bi, wi)
        if result is False:
            # 检测双发扳机
            result, differ = Game.trigger(bi, wi)
        # 拿对应参数
        gun = weapon.get(str(bi)).get(str((wi + differ) if result else wi))
        data[cfg.shake] = gun.get(cfg.shake)  # 记录当前武器抖动参数
        data[cfg.restrain] = gun.get(cfg.restrain)  # 记录当前武器压制参数
        t2 = time.perf_counter_ns()
        print(f'耗时: {
      
      t2-t1}ns, 约{
      
      (t2-t1)//1000000}ms, {
      
      gun.get(cfg.name)}')

apex.py

import multiprocessing
import time
from multiprocessing import Process

import pynput  # conda install pynput

from toolkit import Mouse, Game

end = 'end'
fire = 'fire'
shake = 'shake'
speed = 'speed'
count = 'count'
switch = 'switch'
restart = 'restart'
restrain = 'restrain'
strength = 'strength'
init = {
    
    
    end: False,  # 退出标记, End 键按下后改为 True, 其他进程线程在感知到变更后结束自身
    switch: True,  # 检测和压枪开关
    fire: False,  # 开火状态
    shake: None,  # 抖枪参数
    restrain: None,  # 压枪参数
}


def listener(data):

    def down(x, y, button, pressed):
        if data.get(end):
            return False  # 结束监听线程
        if button == pynput.mouse.Button.right:
            if pressed:
                Game.detect(data)
        elif button == pynput.mouse.Button.left:
            data[fire] = pressed

    mouse = pynput.mouse.Listener(on_click=down)
    mouse.start()

    def release(key):
        if key == pynput.keyboard.Key.end:
            # 结束程序
            data[end] = True
            return False
        elif key == pynput.keyboard.Key.home:
            # 压枪开关
            data[switch] = not data.get(switch)
        elif key == pynput.keyboard.Key.esc:
            Game.detect(data)
        elif key == pynput.keyboard.Key.tab:
            Game.detect(data)
        elif key == pynput.keyboard.Key.alt_l:
            Game.detect(data)
        elif key == pynput.keyboard.KeyCode.from_char('1'):
            Game.detect(data)
        elif key == pynput.keyboard.KeyCode.from_char('2'):
            Game.detect(data)
        elif key == pynput.keyboard.KeyCode.from_char('3'):
            Game.detect(data)
        elif key == pynput.keyboard.KeyCode.from_char('e'):
            Game.detect(data)
        elif key == pynput.keyboard.KeyCode.from_char('r'):
            Game.detect(data)
        elif key == pynput.keyboard.KeyCode.from_char('v'):
            Game.detect(data)

    keyboard = pynput.keyboard.Listener(on_release=release)
    keyboard.start()
    keyboard.join()  # 卡住监听进程, 当键盘线程结束后, 监听进程才能结束


def suppress(data):
    while True:
        if data.get(end):
            break
        if data.get(switch) is False:
            continue
        if data.get(fire):
            if data.get(restrain) is not None:
                for item in data.get(restrain):
                    if not data.get(fire):  # 停止开火
                        break
                    t1 = time.perf_counter_ns()
                    if not Game.game():  # 不在游戏中
                        break
                    if not Game.armed():  # 未持有武器
                        break
                    if Game.empty():  # 弹夹为空
                        break
                    t2 = time.perf_counter_ns()
                    # operation: # 1:移动 2:按下
                    operation = item[0]
                    if operation == 1:
                        temp, x, y, delay = item
                        Mouse.move(x, y)
                        time.sleep((delay - (t2 - t1) // 1000 // 1000) / 1000)
                    elif operation == 2:
                        temp, code, delay = item
                        Mouse.click(code)
                        time.sleep((delay - (t2 - t1) // 1000 // 1000) / 1000)
            elif data.get(shake) is not None:
                total = 0  # 总计时 ms
                delay = 1  # 延迟 ms
                pixel = 4  # 抖动像素
                while True:
                    if not data[fire]:  # 停止开火
                        break
                    if not Game.game():  # 不在游戏中
                        break
                    if not Game.armed():  # 未持有武器
                        break
                    if Game.empty():  # 弹夹为空
                        break
                    t = time.perf_counter_ns()
                    if total < data[shake][speed] * data[shake][count]:
                        Mouse.move(0, data[shake][strength])
                        time.sleep(delay / 1000)
                        total += delay
                    else:
                        Mouse.move(0, 1)
                        time.sleep(delay / 1000)
                        total += delay
                    # 抖枪
                    Mouse.move(pixel, 0)
                    time.sleep(delay / 1000)
                    total += delay
                    Mouse.move(-pixel, 0)
                    time.sleep(delay / 1000)
                    total += delay
                    total += (time.perf_counter_ns() - t) // 1000 // 1000


if __name__ == '__main__':
    multiprocessing.freeze_support()  # windows 平台使用 multiprocessing 必须在 main 中第一行写这个
    manager = multiprocessing.Manager()
    data = manager.dict()  # 创建进程安全的共享变量
    data.update(init)  # 将初始数据导入到共享变量
    # 将键鼠监听和压枪放到单独进程中跑
    p1 = Process(daemon=True, target=listener, args=(data,))  # 监听进程
    p2 = Process(target=suppress, args=(data,))  # 压枪进程
    p1.start()
    p2.start()
    p1.join()  # 卡住主进程, 当进程 listener 结束后, 主进程才会结束

Embalaje y uso

La cuarta etapa realiza la detección de objetivos de IA, mueve el mouse y se despide por completo de la pistola de presión.

Registro de todo el proceso de autoapuntado de Python Apex Legends AI

Supongo que te gusta

Origin blog.csdn.net/mrathena/article/details/126918389
Recomendado
Clasificación