当前位置:首页 » 《随便一记》 » 正文

Python Apex Legends 武器自动识别与压枪 全过程记录

27 人参与  2022年09月22日 15:47  分类 : 《随便一记》  评论

点击全文阅读


博文目录

文章目录

环境准备操纵键鼠驱动安装 链接库加载 代码准备和游戏外测试toolkit.py 游戏内测试 键鼠监听武器识别如何简单且高效判断是否在游戏内如何简单且高效判断背包状态 无武器/1号武器/2号武器如何简单且高效判断武器子弹类别如何简单且高效判断武器名称如何简单且高效判断武器模式 全自动/连发/单发何时触发识别 压枪思路组织数据 第一阶段实现 能自动识别出所有武器cfg.pytoolkit.pyapex.py 第二阶段实现 能自动识别出所有武器并采用对应抖枪参数执行压枪cfg.pytoolkit.pyapex.py 第三阶段实现 放弃抖枪术 转常规后座抵消法 (进行中)如何调压枪参数游戏中实测存在的问题详细代码cfg.pytoolkit.pyapex.py 打包与使用 第四阶段实现 AI 目标检测, 移动鼠标, 彻底告别压枪


本文为下面参考文章的学习与实践

[原文] FPS游戏自动枪械识别+压枪(以PUBG为例)
[转载] FPS游戏自动枪械识别+压枪(以PUBG为例)

环境准备

Python Windows 开发环境搭建

conda create -n apex python=3.9

操纵键鼠

由于绝地求生屏蔽了硬件驱动外的其他鼠标输入,因此我们无法直接通过py脚本来控制游戏内鼠标操作。为了实现游戏内的鼠标下移,我使用了罗技鼠标的驱动(ghub),而py通过调用ghub的链接库文件,将指令操作传递给ghub,最终实现使用硬件驱动的鼠标指令输入给游戏,从而绕过游戏的鼠标输入限制。值得一提的是,我们只是通过py代码调用链接库的接口将指令传递给罗技驱动的,跟实际使用的是何种鼠标没有关系,所以即便用户使用的是雷蛇、卓威、双飞燕等鼠标,对下面的代码并无任何影响。

驱动安装 链接库加载 代码准备和游戏外测试

罗技驱动使用 LGS_9.02.65_X64(请自行找资源安装,官网新版罗技驱动没找到对应的链接库文件),链接库文件在项目链接里面可以找到。下面是载入链接库的代码。

罗技驱动分LGS(老)和GHub(新), 必须装指定版本的LGS驱动(如已安装GHub可能需要卸载), 不然要么报未安装, 要么初始化成功但调用无效

LGS_9.02.65_x64_Logitech.exe, 网盘下载
其他地址1
其他地址2

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

装了该驱动后, 无需重启电脑, 当下就生效了. 遗憾的是, 没有对应的文档, 只能猜测参数了

toolkit.py

import timefrom ctypes import CDLLimport win32api  # conda install pywin32try:    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)

游戏内测试

在游戏里面试过后, 管用, 但是不准, 猜测可能和游戏内鼠标灵敏度/FOV等有关系

from toolkit import Mouseimport pynput  # conda install pynputdef 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()

键鼠监听

前面说到,要实现压枪就要对各种配件、状态做出识别。那么在写识别的函数之前,我们先要解决的是何时识别的问题。如果识别使用多线程\多进程的一直持续检测,无疑是一种巨大的开销,因此就需要对键盘、鼠标的状态进行监听。只有按下特定按键时,才触发特定相应的识别请求。

这里我使用的钩子是Pynput,其他可使用的库还有Pyhook3

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

注意调试回调方法的时候, 不要打断点, 不要打断点, 不要打断点, 这样会卡死IO, 导致鼠标键盘失效

Listener中绑定on_press和on_release的函数( on_key_press、on_key_release),它们返回False的时候是结束监听,下文鼠标监听的函数同理,所以不要随便返回False

键盘的特殊按键采用keyboard.Key.tab这种写法,普通按键用keyboard.KeyCode.from_char(‘c’)这种写法

有些键不知道该怎么写, 可以 print(key) 查看写法

这里有一点非常坑,on_press和on_release的参数只能有一个key,这个key就是对应键盘按下的哪颗按键。但这是不足以满足我们的需求的,因为我们应该在钩子函数内部,在按下指定按键时对信号量做出修改,但因为参数的限制,我们无法把信号量传进函数内部,这里我也是想了很久,最后才想到用嵌套函数的写法解决这个问题。

另外,钩子函数本身是阻塞的。也就是说钩子函数在执行的过程中,用户正常的键盘/鼠标操作是无法输入的。所以在钩子函数里面必须写成有限的操作(即O(1)时间复杂度的代码),也就是说像背包内配件及枪械识别,还有下文会讲到的鼠标压枪这类时间开销比较大或者持续时间长的操作,都不适合写在钩子函数里面。这也解释了为什么在检测到Tab(打开背包)、鼠标左键按下时,为什么只是改变信号量,然后把这些任务丢给别的进程去做的原因。

武器识别

在这里插入图片描述

如何简单且高效判断是否在游戏内

找几个特征点取色判断, 血条左上角和生存物品框左下角

一般能用于取色的点, 它的颜色RGB都是相同的, 这种点的颜色非常稳定

我原本以为屏幕点取色应该不会超过1ms的耗时, 结果万万没想到, 取个色居然要1-10ms, 效率奇低, 暂无其他优雅方法

如何简单且高效判断背包状态 无武器/1号武器/2号武器

在这里插入图片描述

看武器边框上红色圈住的部分颜色, 灰色说明没有武器, 上下不同色说明使用2号武器, 上下同色说明使用1号武器

如何简单且高效判断武器子弹类别

因为不同子弹类型的武器, 边框颜色不一样. 所以可以和上面的放在一起, 同一个点直接判断出背包状态和子弹类别

如何简单且高效判断武器名称

在分类后的基础上, 通过 背包状态 确定要检查颜色的位置(1号位/2号位), 通过 武器子弹类别 缩小判断范围, 在每个武器的名字上找一个纯白色的点, 确保这个点只有这把武器是纯白色, 然后逐个对比

如何简单且高效判断武器模式 全自动/连发/单发

在这里插入图片描述
需要压枪的只有全自动和半自动两种模式的武器, 单发不需要压枪(后面有可能做自动单发, 到时候在考虑), 喷子和狙不需要压枪

所以需要找一个能区分三种模式的点(不同模式这个点的颜色不同但是稳定), 且这个点不能受和平和三重的特殊标记影响

一开始找了个不是纯白色的点, 后来发现这个点的颜色会被背景颜色影响到, 不是恒定不变的. 最终还是放弃了一个点即可分辨模式的想法, 采用了稳妥的纯白色点, 保证该点只在该模式下是纯白色的, 在其他模式都不是纯白色即可

收起武器, 部分武器可以通过[V]标判断, 放弃

何时触发识别

鼠标右键 按下, 识别武器. 和游戏内原本的按键功能不冲突1/2/3/E/V/Tab/Esc/Alt 键释放, 识别武器Home 键释放, 切换开关end 键释放, 结束程序

压枪思路

apex 的压枪有3个思路, 因为 apex 不同武器的弹道貌似是固定的, 没有随机值?, 其他游戏也是??

左右抖动抵消水平后坐力, 下拉抵消垂直后坐力. 这种方法简单, 但是画面会抖动, 效果也不是很好根据武器配件等测试不同情况下的武器后坐力数据, 然后做反向抵消.
可以通过取巧的方式, 只做无配件状态下的反向抵消, 还省了找配件的麻烦
这种方法太难太麻烦了, 但是做的好的话, 基本一条线, 强的离谱还有就是现在很火的AI目标检测(yolov5), 我也有尝试做, 环境搭好了, 但是中途卡住了. 一是毕竟python是兴趣, 很多基础不到位, 相关专业知识更是空白, 参考内容也参差不齐, 导致对检测和训练的参数都很模糊. 二是数据集采集, 网上找了些, 自己做了些, 但是任然只有一点点, 不着急, 慢慢找吧. 据说要想效果好, 得几千张图片集 …

我先试试 抖枪大法

组织数据

武器数据, 通过子弹类型分组, 组里的每个成员指定序号, 名称, 压枪参数等信息

配置数据, 按分辨率分组, 再按是否在游戏中, 是否有武器, 武器位置, 武器子弹类型, 武器索引等信息分类

信号数据, 程序运行时, 进程线程间通讯

第一阶段实现 能自动识别出所有武器

目前测试下来, 一波识别大概六七十毫秒的样子, 最多也不会超过一百毫秒, 主要耗时在取色函数(1-10ms), 性能已经够用了

我的配置: AMD R7 2700x, Nvidia RTX 2080, 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: '暴走',        },    }}

toolkit.py

import mss  # pip install mssimport ctypesfrom ctypes import CDLLimport cfgfrom cfg import detect, weapon# 全局 dlluser32 = ctypes.windll.user32gdi32 = ctypes.windll.gdi32hdc = 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, hclass 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 timeimport pynput  # conda install pynputimport toolkitExitFlag = Falsedef 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()

在这里插入图片描述

第二阶段实现 能自动识别出所有武器并采用对应抖枪参数执行压枪

在这里插入图片描述

游戏内鼠标灵敏度越高越容易抖枪且效果更好, 但是开到5的话, 会感到有点晕游戏内鼠标灵敏度越高, 代码里抖动的像素就需要设置的更小, 比如5的灵敏度, 抖动2像素就可以了抖枪能减小后坐力, 但不能完全消除, 所以还需配合对应方向的移动

在2.5灵敏度下, 301使用下面这个参数, 二三十米还行, 五十米, 三倍效果还将就一倍就很差了. 后坐力越大的武器, 前几枪容易跳太高, 下压力度可以大点

还有就是延迟要低一点, 我这边裸连延迟300+, 经常子弹打出去, 过半秒才减血, 这样很难测的准

能量武器, 专注和哈沃克, 预热和涡轮有很大影响, 这里没管, 将就将就

total = 0  # 总计时 msdelay = 1  # 延迟 mspixel = 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 timeimport mss  # pip install mssimport ctypesfrom ctypes import CDLLimport cfgfrom cfg import detect, weapon# 全局 dlluser32 = ctypes.windll.user32gdi32 = ctypes.windll.gdi32try:    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, hclass 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 multiprocessingimport timefrom multiprocessing import Processimport pynput  # conda install pynputimport toolkitend = '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 // 1000if __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 结束后, 主进程才会结束

第三阶段实现 放弃抖枪术 转常规后座抵消法 (进行中)

调了几把枪, 有无涡轮, 有无双发扳机, 有压枪参数的使用压枪参数, 其他的使用抖枪参数, 压枪与抖枪并存

我的游戏内鼠标设置是这样的, 要确保每个瞄镜的ADS都是一样的, 鼠标DPI是3200

最终的效果是, 20米很稳, 30米将就, 50米不太行, 有几率一梭子打倒, 再往后就没意义了. 差不多够用了, 就没再认真调
在这里插入图片描述

如何调压枪参数

我觉得调参数最重要的一点, 就是先算出正确的子弹射速(平均每发子弹耗时), 如果用了错误的数据, 那很可能调了半天白费功夫

测试方法我总结了下, 首先, 每发子弹耗时通常都是50到150毫秒, 先假设是100, 看有多少发子弹, 就复制多少条压枪数据, 举例

R-301 这把枪, 加上金扩容, 28发子弹, 那就先准备下面的初始数据, 三个参数分别是, 鼠标水平移动的值/垂直移动的值/移动后休眠时间, 当然也可以有其他的参数

先把对应最后一发子弹的鼠标移动值设置为10000, 看是否打完子弹时, 鼠标正好产生大幅位移, 然后调后面的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],[0, 0, 100],[10000, 0, 100],

调鼠标参数时, 要从上往下逐个调, 因为上面的一个变动, 对下面的影响非常大, 很可能导致下面的白调了

比如调纵向压制的时候, 1倍镜30米瞄这这道杠打, 争取基本全都在杠上, 纵向就ok了, 横向同理

在这里插入图片描述

也可以借助录像工具, 录制屏幕中心部分区域, 然后以0.1倍速播放, 仔细查看压制力度是否合适

最终的效果就是, 不太稳定, 123倍镜表现不太一致, 3倍镜偏差最大. 难不成各个镜子做一套参数?

游戏中实测

平行是真的准, 301也将就

就是跑一会儿就变得很卡了, 我怀疑是压枪进程的原因, 所以想加一个重启压枪进程的开关试试效果

结果没按我想的来, 每重启一下, 多一个进程, 老进程也没有消失 …

存在的问题

采用取色判断法, 单点取色耗时1-10ms, 性能不足检测武器使用的是O(n)时间复杂度的方式, 期望做到O(1)暂无法判断是否持有武器(有武器但我用拳头, 可能引起错误地触发压枪), V取色判断效率不能保证运行一会儿就会卡顿, 重启程序立马流畅, 怀疑是压制进程里循环的原因, 但暂不能重启进程

详细代码

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'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: {  # 是否持有武器(比如有武器但用拳头就是未持有武器)        },        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: [                [80, -2, 10],  #                [80, -2, 10],                [80, -2, 10],                [80, -4, 10],                [80, -6, 10],                [80, -7, 8],  #                [80, -7, 8],                [80, -7, 8],                [80, -7, 8],                [80, -7, 8],                [80, -1, 5],  #                [80, -1, 5],                [80, -1, 5],                [80, -1, 5],                [80, -1, 5],                [80, -1, 5],  #                [80, -1, 3],                [80, -1, 3],                [80, -1, 3],                [80, -1, 3],                [80, -1, 3],  #                [80, -2, 3],                [80, -2, 3],                [80, -2, 3],                [80, -2, 3],                [80, -5, 3],  #                [80, -5, 3],                [80, -10, 3],                [80, -10, 3],                [80, -10, 3],            ]        },        '2': {            name: '转换者冲锋枪',            shake: {                speed: 100,                count: 10,                strength: 7,            }        },        '3': {            name: 'R-301 卡宾枪',            shake: {                speed: 64,  # 74ms打一发子弹                count: 6,  # 压制前6发                strength: 5,  # 压制的力度(下移的像素)            },            restrain: [                [-5, 10, 70],                [0, 10, 70],                [-5, 10, 70],                [-2, 10, 70],                [0, 10, 70],  #                [0, 5, 70],                [0, 0, 70],                [-5, 0, 70],                [-5, 5, 70],                [0, 0, 70],  #                [0, 0, 70],                [5, 10, 70],                [5, 5, 70],                [5, 0, 70],                [5, 0, 70],  #                [0, 0, 70],                [5, 0, 70],                [5, 10, 70],                [0, 10, 70],                [-5, 0, 70],  #                [-5, 0, 70],                [-5, 0, 70],                [-5, 0, 70],                [-5, 0, 70],                [0, 0, 70],  #                [0, 0, 70],                [0, 0, 70],                [0, 0, 64],            ]        },        '4': {            name: 'R-99 冲锋枪',            shake: {                speed: 55.5,                count: 13,                strength: 8,            }        },        '5': {            name: 'P2020 手枪',        },        '6': {            name: '喷火轻机枪',            shake: {                speed: 110,                count: 8,                strength: 5,            },            restrain: [                [110, 0, 20],                [110, 5, 15],                [110, 5, 15],                [110, 5, 15],                [110, 5, 15],  #                [110, 5, 15],                [110, -5, 10],                [110, -5, 0],                [110, -5, 0],                [110, -5, 0],  #                [110, 0, 0],                [110, 0, 5],                [110, 0, 5],                [110, 5, 5],                [110, 10, 5],  #                [110, 10, 5],                [110, 5, 5],                [110, 0, 5],                [110, 0, 5],                [110, 0, 5],  # 20                [110, 0, 0],                [110, 0, 0],                [110, 0, 0],                [110, 0, 0],                [110, -5, 5],  #                [110, -5, 5],                [110, -5, 5],                [110, -5, 5],                [110, 0, 5],                [110, 0, 5],  #                [110, 0, 5],                [110, 0, 5],                [110, 0, 5],                [110, 0, 5],                [110, 0, 5],  #                [110, 0, 5],                [110, 0, 5],                [110, 0, 5],                [110, 0, 5],                [110, 0, 5],  #                [110, 0, 5],                [110, 0, 5],                [110, 0, 5],                [110, 0, 5],                [110, 0, 5],  #                [110, 0, 5],                [110, 0, 5],                [110, 0, 5],                [110, 0, 5],                [110, 0, 0],  #            ]        },        '7': {            name: 'G7 侦查枪',        },        '8': {            name: 'CAR (轻型弹药)',            shake: {                speed: 64.5,                count: 10,                strength: 7,            },            restrain: [                [58, 0, 10],  #                [58, 3, 10],                [58, 3, 10],                [58, 3, 10],                [58, 3, 10],                [58, 3, 10],  #                [58, 3, 10],                [58, 3, 10],                [58, -5, 10],                [58, -5, 10],                [58, -5, 5],  #                [58, -5, 10],                [58, -5, 0],                [58, 0, 0],                [58, 5, 0],                [58, 5, 3],  #                [58, 5, 3],                [58, -5, 3],                [58, -5, 3],                [58, -5, 3],                [58, 0, 0],  #                [58, 0, 0],                [58, 0, 0],                [58, 0, 3],                [58, 0, 3],                [58, 0, 3],  #                [58, 0, 3],            ]        },        '9': {            name: 'G7 侦查枪 (双发扳机)',            restrain: [                [20, 0, 5]            ]        },    },    '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: [                [100, 0, 10],  #                [100, 5, 10],                [100, 5, 10],                [100, 5, 10],                [100, 5, 10],                [100, -5, 10],  #                [100, -5, 0],                [100, -5, 0],                [100, -5, 0],                [100, 0, 5],                [100, 5, 5],  #                [100, 5, 5],                [100, 5, 0],                [100, 5, 0],                [100, 0, 0],                [100, 5, 5],  #                [100, 5, 5],                [100, 5, 5],                [100, 0, 0],                [100, 0, 0],                [100, -5, 5],  #                [100, -5, 5],                [100, -5, 5],                [100, -0, 5],                [100, 5, 5],                [100, 5, 5],  #                [100, 5, 5],                [100, -5, -5],                [100, -5, 5],                [100, -5, 5],            ]        },        '4': {            name: '30-30',        },        '5': {            name: 'CAR (重型弹药)',            shake: {                speed: 58,                count: 10,                strength: 7,            },            restrain: [                [58, 0, 10],  #                [58, 3, 10],                [58, 3, 10],                [58, 3, 10],                [58, 3, 10],                [58, 3, 10],  #                [58, 3, 10],                [58, 3, 10],                [58, -5, 10],                [58, -5, 10],                [58, -5, 5],  #                [58, -5, 10],                [58, -5, 0],                [58, 0, 0],                [58, 5, 0],                [58, 5, 3],  #                [58, 5, 3],                [58, -5, 3],                [58, -5, 3],                [58, -5, 3],                [58, 0, 0],  #                [58, 0, 0],                [58, 0, 0],                [58, 0, 3],                [58, 0, 3],                [58, 0, 3],  #                [58, 0, 3],            ]        }    },    '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,            },            restrain: [                [400, 0, 0],  # 延迟                [88, -5, 10],  # 1                [88, -5, 15],                [88, 0, 15],                [88, 0, 15],                [88, 0, 15],                [88, 5, 10],  #                [88, 5, 10],                [88, 5, 10],                [88, 5, 10],                [88, -5, 5],                [88, -5, 0],  # 1                [88, -5, 0],                [88, -10, 0],                [88, -10, 0],                [88, -5, 0],                [88, 0, 5],  #                [88, 10, 5],                [88, 10, 5],                [88, 0, 0],                [88, 0, 0],                [88, 5, 10],  # 1                [88, 5, 10],                [88, 0, 10],                [88, 5, 10],                [88, 5, 10],                [88, 5, 10],  #                [88, 5, 5],                [88, 5, 5],                [88, 0, 5],                [88, 0, 0],                [88, 0, 0],  # 1                [88, 0, 0],                [88, 0, 5],                [88, 0, 5],                [88, 0, 5],                [88, 0, 5],  #            ]        },        '6': {            name: '专注轻机枪 (涡轮)',            shake: {                speed: 100,                count: 10,                strength: 7,            }        },        '7': {            name: '哈沃克步枪 (涡轮)',            shake: {                speed: 100,                count: 8,                strength: 6,            },            restrain: [                [88, -5, 10],  # 1                [88, -5, 15],                [88, 0, 15],                [88, 0, 15],                [88, 0, 15],                [88, 5, 10],  #                [88, 5, 10],                [88, 5, 10],                [88, 5, 10],                [88, -5, 5],                [88, -5, 0],  # 1                [88, -5, 0],                [88, -10, 0],                [88, -10, 0],                [88, -5, 0],                [88, 0, 5],  #                [88, 10, 5],                [88, 10, 5],                [88, 0, 0],                [88, 0, 0],                [88, 5, 10],  # 1                [88, 5, 10],                [88, 0, 10],                [88, 5, 10],                [88, 5, 10],                [88, 5, 10],  #                [88, 5, 5],                [88, 5, 5],                [88, 0, 5],                [88, 0, 0],                [88, 0, 0],  # 1                [88, 0, 0],                [88, 0, 5],                [88, 0, 5],                [88, 0, 5],                [88, 0, 5],  #            ]        },    },    '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 timeimport mss  # pip install mssimport ctypesfrom ctypes import CDLLimport cfgfrom cfg import detect, weapon# 全局 dlluser32 = ctypes.windll.user32gdi32 = ctypes.windll.gdi32try:    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, hclass 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 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 multiprocessingimport timefrom multiprocessing import Processimport pynput  # conda install pynputfrom toolkit import Mouse, Gameend = 'end'fire = 'fire'shake = 'shake'speed = 'speed'count = 'count'switch = 'switch'restart = 'restart'restrain = 'restrain'strength = 'strength'init = {    end: False,  # 退出标记, End 键按下后改为 True, 其他进程线程在感知到变更后结束自身    restart: False,  # 重启压制进程标记, 感觉卡顿时重启    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.page_down:            # 重启压枪进程            data[restart] = True            # restart(data)        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('v'):            Game.detect(data)    keyboard = pynput.keyboard.Listener(on_release=release)    keyboard.start()    keyboard.join()  # 卡住监听进程, 当键盘线程结束后, 监听进程才能结束def suppress(data):    data[restart] = False    while True:        if data.get(end):            break        if data.get(restart):            break        if data.get(switch) is False:            continue        if Game.game() & Game.armed() & data.get(fire):            if data.get(restrain) is not None:                for item in data.get(restrain):                    if not data.get(fire):                        break                    x, y, delay = item                    Mouse.move(x, y)                    time.sleep(delay / 1000)            elif data.get(shake) is not None:                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]:                        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 // 1000if __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,))  # 监听进程    p1.start()    p2 = Process(target=suppress, args=(data,))  # 压枪进程    p2.start()    p1.join()  # 卡住主进程, 当进程 listener 结束后, 主进程才会结束

打包与使用

第四阶段实现 AI 目标检测, 移动鼠标, 彻底告别压枪

Python Apex Legends AI 自瞄 全过程记录


点击全文阅读


本文链接:http://zhangshiyu.com/post/45030.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1