Python Apex Legends weapon automatic identification and full process record of gun pressure

Blog post directory


This article is the study and practice of the following reference articles

[Original] FPS game automatic firearm recognition + pressure gun (taking PUBG as an example)
[Reprint] FPS game automatic firearm recognition + pressure gun (taking PUBG as an example)

Environmental preparation

Python Windows development environment to build

conda create -n apex python=3.9

Manipulate the keyboard and mouse

Since PUBG blocks other mouse input outside the hardware driver, we cannot directly control the mouse operation in the game through the py script. In order to realize the mouse down in the game, I used the Logitech mouse driver (ghub), and py passed the command operation to ghub by calling the link library file of ghub, and finally realized the use of hardware-driven mouse commands to input to the game, thus Bypass the game's mouse input restrictions. It is worth mentioning that we only pass the instructions to the Logitech driver through the py code calling the interface of the link library, which has nothing to do with the actual mouse used, so even if the user uses Razer, Zhuowei, Shuangfeiyan, etc. The mouse, has no effect on the code below.

Driver installation link library loading code preparation and out-of-game testing

The Logitech driver uses LGS_9.02.65_X64 (please find the resources to install by yourself, the new version of the Logitech driver on the official website does not find the corresponding link library file). The link library file can be found in the project link. Below is the code to load the link library.

Logitech drivers are divided into LGS (old) and GHub (new), the specified version of LGS driver must be installed (if GHub is installed, it may need to be uninstalled), otherwise it will either report that it is not installed, or the initialization is successful but the call is invalid.

LGS_9.02.65_x64_Logitech.exe, download from network disk

mouse.device.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('缺少文件')

After the driver is installed, it will take effect immediately without restarting the computer. Unfortunately, there is no corresponding documentation for the methods in this dll file, so we can only guess the parameters.

toolkit.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)

in-game testing

After trying it in the game, it works, but it is not allowed, guess it may be related to the mouse sensitivity/FOV in the game, 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()

Keyboard and mouse monitoring

As mentioned earlier, in order to realize the pressure gun, it is necessary to identify various accessories and states. So before writing the recognition function, we need to solve the problem of when to recognize. It is undoubtedly a huge overhead to identify the continuous detection of multi-threaded/multi-process, so it is necessary to monitor the status of the keyboard and mouse. A specific corresponding identification request is triggered only when a specific key is pressed.

The hook I use here is Pynput, and other libraries that can be used are Pyhook3

Description of 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()

Note that when debugging the callback method, don't break the point, don't break the point, don't break the point, this will block the IO and cause the mouse and keyboard to fail.

The functions of on_press and on_release (on_key_press, on_key_release) are bound in the Listener. When they return False, they end the monitoring. The same is true for the mouse monitoring functions below, so don’t just return False.

The special keys of the keyboard are written in keyboard.Key.tab, and the ordinary keys are written in keyboard.KeyCode.from_char('c').

Some keys do not know how to write, you can print(key)check spelling

There is a very pit here. The parameters of on_press and on_release can only have one key, and this key is the key pressed on the corresponding keyboard. But this is not enough to meet our needs, because we should modify the semaphore inside the hook function when the specified button is pressed, but due to the limitation of parameters, we cannot pass the semaphore into the function, here I I also thought about it for a long time, and finally thought of using the nested function to solve this problem.

Also, the hook function itself is blocking. That is to say, during the execution of the hook function, the user's normal keyboard/mouse operations cannot be input. Therefore, the hook function must be written as a limited operation (that is, the code with O(1) time complexity), which means that the time overhead such as the identification of accessories in the backpack and firearms, and the mouse pressure gun mentioned below is relatively large. Or long-duration operations are not suitable for writing in hook functions. This also explains why when Tab (opening the backpack) and the left mouse button are detected, the semaphore is only changed, and then these tasks are left to other processes to do.

weapon identification

insert image description here

How to easily and efficiently determine whether you are in-game or not

First, determine whether the game window is at the forefront, and then determine whether the gun interface is being held in the game.

Find a few feature points to judge the color, the upper left corner of the health bar and the lower left corner of the survival item box

Generally, the point that can be used for color picking, its color RGB is the same, the color of this point is very stable

I originally thought that it would take no more than 1ms to select a color on the screen, but I never expected that it would take 1-10ms to select a color, which is extremely inefficient, and there is no other elegant method.

How to simply and efficiently judge the backpack status without weapon/weapon 1/weapon 2

insert image description here

Look at the color of the part circled in red on the weapon frame, gray means no weapon, different colors up and down indicate the use of the No. 2 weapon, and the same color up and down means that the No. 1 weapon is used

How to easily and efficiently determine the weapon bullet type light/heavy/energy/sniper/shot/airdrop

Because weapons of different bullet types have different border colors. So it can be put together with the above, and the same point can directly determine the status of the backpack and the type of bullets

How to easily and efficiently determine the name of a weapon

On the basis of classification, determine the position to check the color (position 1/position 2) by the status of the backpack, narrow the judgment range by the weapon bullet category, and find a pure white point on the name of each weapon to ensure this Point only this weapon is pure white, and then compare them one by one

How to easily and efficiently judge the weapon mode fully automatic/burst/single

insert image description here
The only weapons that need to press the gun are fully automatic and semi-automatic. Single shot does not need to press the gun (it is possible to do automatic single shot later, which will be considered when the time comes), and the gun and sniper do not need to press the gun.

So you need to find a point that can distinguish the three modes (the color of this point is different but stable in different modes), and this point cannot be affected by the special marks of peace and triple

At first, I found a point that was not pure white. Later, I found that the color of this point will be affected by the background color, which is not constant. In the end, I gave up the idea of ​​​​distinguishing the mode with one point, and adopted a stable pure white point. , to ensure that the point is pure white only in this mode, and not pure white in other modes

How to easily and efficiently determine whether you have a weapon

It is impossible to judge for the time being. There is no fixed point that can clearly distinguish between the two situations when the weapon is put away and the weapon is held.

Some weapons can be judged by the [V] mark, because they are incomplete, they will not be used first

You can also set the mark by monitoring and pressing the [3] key (the operation of retracting the weapon), other operations remove the mark, and then read the mark to determine whether you have a weapon, but it is not elegant, so don’t use it first

The current conclusion is that when using a fist, the crosshair is a large square crosshair, and when using a weapon, it is a round crosshair. But using a fist does not mean not holding a weapon

How to easily and efficiently determine whether the magazine is empty

insert image description here
The number of bullets in the magazine is mostly two digits (LSTAR may be three digits), so just confirm that the tens digit is not 0, it can be considered not empty, the tens digit is 0 and the ones digit is 0, it can be considered empty

  • The tens point is in the middle of the number, 1-9 are pure white, 0 is gray. Note that this gray is not a fixed color, the color will change as the background changes
  • The point of the one's digit, at the leftmost end of the slash in the middle of the number 0, this point is pure white, and when other 1-9, this point is not pure white

When to trigger recognition

  • Press the right button of the mouse to identify the weapon. It does not conflict with the original button function in the game
  • 1/2/3/E/V/R/Tab/Esc/Alt key release, identify weapon
  • Home key release, toggle switch
  • end key release, end the program

a few details

  • Through testing, it is found that the firing interval of all weapons is greater than 50 milliseconds, so when pressing the gun, you can do some operations within 50 milliseconds, such as judging whether the magazine is empty, to avoid triggering the pressing gun

pressure gun ideas

There are 3 ideas for apex's pressure gun, because the ballistics of apex's different weapons seem to be fixed, there is no random value?, and other games too??

  • The left and right shaking counteracts the horizontal recoil, and the pull-down counteracts the vertical recoil. This method is simple, but the picture will shake and the effect is not very good
  • Test the recoil data of the weapon under different conditions according to the weapon accessories, etc., and then do the reverse offset.
    You can use a tricky way to only do the reverse offset without the accessories, and save the trouble of finding accessories.
    This method is too difficult and too troublesome Yes, but if done well, it's basically a line, and it's outrageously strong
  • There is also the very popular AI target detection (yolov5), which I also tried to do. The environment was set up, but it got stuck in the middle. First, after all, python is an interest, many basics are not in place, and relevant professional knowledge is even blank. For reference The content is also uneven, which makes the parameters of detection and training very vague. The second is the collection of data sets. I found some on the Internet and made some myself, but there is still only a little bit. Don't worry, look for it slowly. If you want to get a good effect, you need thousands of pictures...

Organizing data

Weapon data, grouped by bullet type, each member in the group specifies the serial number, name, gun pressure parameters and other information

Configuration data, grouped by resolution, and then classified by whether it is in the game, whether there is a weapon, weapon location, weapon bullet type, weapon index, etc.

Signal data, program runtime, inter-process thread communication

The first stage implementation can automatically identify all weapons

At present, after testing, a wave of recognition is about 60 to 70 milliseconds, and it will not exceed 100 milliseconds at most. The main time is spent on the color selection function (1-10ms), and the performance is sufficient.

My configuration: AMD R7 2700x, Nvidia RTX 2080, 3440*1440 resolution

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: '暴走',
        },
    }
}

toolkit.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()

insert image description here

The second stage realizes that the corresponding gun shaking parameters can be automatically used to execute the pressure gun

insert image description here

  • The higher the mouse sensitivity in the game, the easier it is to shake the gun and the effect is better, but if you open it to 5, you will feel a little dizzy
  • The higher the mouse sensitivity in the game, the smaller the jittered pixels in the code need to be set. For example, if the sensitivity is 5, the jitter is 2 pixels.
  • Shaking the gun can reduce the recoil, but it cannot completely eliminate it, so it needs to be moved in the corresponding direction

At 2.5 sensitivity, 301 uses the following parameters, 20 to 30 meters is ok, 50 meters, and triple the effect will be doubled, which is very bad. The weapon with greater recoil, the first few shots are easy to jump too high, and the next shot is too high. The pressure can be higher

Also, the delay should be lower. I have a naked connection delay of 300+ here. I often shoot out bullets, and the blood is reduced after half a second, so it is difficult to measure the accuracy.

Energy weapons, focus and havok, preheat and turbo have a big impact, don't care here, it will be soon

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,
            }
        },
    }
}

toolkit.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 结束后, 主进程才会结束

The third stage realizes the abandonment of the gun shaking technique and the conventional recoil offset method (in optimization)

Adjusted a few guns, with or without turbo, with or without dual triggers, with pressure gun parameters using pressure gun parameters, others using gun shaking parameters, pressure gun and gun shaking coexist

My in-game mouse settings are like this, make sure that the ADS of each scope is the same, the mouse DPI is 3200

The final effect is that 20 meters is very stable, 30 meters will be close, 50 meters is not enough, there is a chance of being knocked down in one shuttle, and then it will be meaningless.
insert image description here

How to adjust the gun parameters

I think the most important point in adjusting parameters is to first calculate the correct rate of fire of bullets (average time per bullet).

I summarize the test method. First of all, each bullet usually takes 50 to 150 milliseconds. Let’s assume that it is 100. To see how many bullets there are, copy as many gun pressure data as possible. For example

R-301 This gun, plus gold expansion, 28 bullets, then prepare the following initial data first, the three parameters are, the value of the mouse horizontal movement / the value of vertical movement / sleep time after moving, of course, you can also with other parameters

First, set the mouse movement value corresponding to the last bullet to 10000, to see if the mouse has a large displacement when the bullet is fired, and then adjust the latter 100 until it just matches, and then you can start to adjust the mouse parameters.

[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],

When adjusting the mouse parameters, you should adjust them one by one from top to bottom, because a change on the top has a great impact on the bottom, and it is likely to cause the white tone below.

For example, when adjusting the vertical suppression, the 1x mirror is aimed at this bar at 30 meters, and strive to basically be on the bar, the vertical is ok, and the horizontal is the same.

insert image description here

You can also use the video recording tool to record part of the central area of ​​the screen, and then play it at 0.1 times speed, and carefully check whether the suppression strength is appropriate

The final effect is that it is not very stable, the performance of the 123x mirror is not consistent, and the deviation of the 3x mirror is the largest. Is it difficult to make a set of parameters for each mirror?

In-game test

On the whole, the performance will be fine, the parallel is ok, the others are average, it is still far from a line

existing problems

  • Using the color-picking judgment method, the single-point color-picking takes 1-10ms, and the performance is insufficient
  • The detection of the weapon name uses the traversal method of O(n) time complexity. In the case of the low efficiency of the color selection judgment method, the performance is not excellent and stable enough, and it is expected to achieve O(1)
  • It is impossible to judge whether there is a weapon (there is a weapon but I use my fist, which may cause the pressure gun to be triggered by mistake)
  • It will freeze after running for a while. It is very stable. It freezes every two seconds on average. All the mouse, keyboard, and computer screens freeze at the same time, which seriously affects the game. I don't know why.
  • It is not possible to simulate the effect of clicking the left button when pressing the left button, so it is temporarily impossible to realize the function of changing a single-shot gun to a burst gun.

Detailed code

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,
            }
        },
    }
}

toolkit.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 结束后, 主进程才会结束

Packaging and use

The fourth stage realizes AI target detection, moves the mouse, and bids farewell to the pressure gun completely

Python Apex Legends AI self-aiming whole process record

Guess you like

Origin blog.csdn.net/mrathena/article/details/126918389