博文目录

文章目录

  • 环境准备
    • 操纵键鼠
      • 驱动安装 链接库加载 代码准备和游戏外测试
        • toolkit.py
      • 游戏内测试
    • 键鼠监听
  • 武器识别
    • 如何判断是否在游戏内
    • 如何判断背包状态 无武器/1号武器/2号武器
    • 如何判断武器子弹类别 轻型/重型/能量/狙击/霰弹/空投
    • 如何判断武器名称 在确定当前使用的背包的基础上判断
    • 如何判断武器模式 全自动/连发/单发
    • 如何判断是否持有武器
    • 如何判断弹夹是否打空
    • 何时触发识别
    • 几个细节点
    • 压枪思路
    • 组织数据
  • 开发过程
    • 第一阶段实现 能自动识别出所有武器
    • 第二阶段实现 能自动采用对应抖枪参数执行压枪
    • 第三阶段实现 能自动采用对应压枪参数执行压枪
      • 如何调压枪参数
      • 游戏中实测
      • 存在的问题
  • 工程源码
    • 相关资源
    • cfg.py
    • toolkit.py
    • apex.py
    • 打包与使用
  • 拓展 目标检测 与 自瞄, 彻底告别压枪
  • 拓展 通用型人体骨骼检测 与 自瞄, 训练一次, FPS 游戏通用

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

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

环境准备

Python Windows 开发环境搭建

conda create -n apex python=3.9

操纵键鼠

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

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

罗技驱动分 LGS (老) 和 GHub (新). LGS 的话, 需要使用 9.02.65 版本的, GHub 的话, 需要使用 2021.11 之前的, 二者自选其一即可

网盘下载 DLL 文件 和 新老罗技驱动

装了该驱动后, 无需重启电脑. hosts 文件添加 127.0.0.1 updates.ghub.logitechg.com 防止更新. 在 右键-属性-安全 里给当前用户授予 hosts 文件的完全访问权限后, 就可以修改保存了

遗憾的是, 这个库没有文档, 只能猜测参数了. 下面是某老哥列出的该库里面的方法

另外需要注意 控制面板 中的 鼠标 属性选项

  • 提高指针精确度 选项去掉, 不然会造成实际移动距离变大
  • 指针移动速度要在 左边到中间, 到右边的话会导致实际移动距离变大, 左边的话会导致指针移动很慢(取坐标不对, 一定时间后才对)


toolkit.py

import ctypesfrom win32gui import GetCursorPos  # conda install pywin32try:driver = ctypes.CDLL('logitech.dll')# 该驱动每个进程可打开一个实例ok = driver.device_open() == 1if not ok:print('Error, GHUB or LGS driver not found')
except FileNotFoundError:print('Error, DLL file not found')class GHub:class mouse:"""code: 1:左键, 2:中键, 3:右键"""@staticmethoddef press(code):if not ok:returndriver.mouse_down(code)@staticmethoddef release(code):if not ok:returndriver.mouse_up(code)@staticmethoddef click(code):if not ok:returndriver.mouse_down(code)driver.mouse_up(code)@staticmethoddef scroll(a):"""a:没搞明白"""if not ok:returndriver.scroll(a)@staticmethoddef move(x, y, absolute=False):"""x: 水平移动的方向和距离, 正数向右, 负数向左y: 垂直移动的方向和距离absolute: 是否绝对移动, 是:跳到水平x和垂直y的位置, 否:水平跳x距离垂直跳y距离"""if not ok:returnif x == 0 and y == 0:returnmx, my = x, yif absolute:ox, oy = GetCursorPos()mx = x - oxmy = y - oydriver.moveR(mx, my, True)class keyboard:"""键盘按键函数中,传入的参数采用的是键盘按键对应的键码code: 'a'-'z':A键-Z键, '0'-'9':0-9, 其他的没猜出来"""@staticmethoddef press(code):if not ok:returndriver.key_down(code)@staticmethoddef release(code):if not ok:returndriver.key_up(code)@staticmethoddef click(code):if not ok:returndriver.key_down(code)driver.key_up(code)

游戏内测试

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

from toolkit import Mouse
import 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 说明

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, 导致鼠标键盘失效

回调方法如果返回 False, 监听线程就会自动结束, 所以不要随便返回 False

键盘的特殊按键采用 keyboard.Key.tab 这种写法,普通按键用 keyboard.KeyCode.from_char('c') 这种写法, 有些键不知道该怎么写, 可以 print(key) 查看信息

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

武器识别

如何判断是否在游戏内

先是判断游戏窗体是否在最前端, 然后判断游戏内是否正在持枪界面

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

一般能用于取色的点, 它的颜色RGB都是相同的, 这种点的颜色非常稳定, 不会受不同背景色的影响

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

如何判断背包状态 无武器/1号武器/2号武器


黄圈内的武器面板, 可以分为两个部分, 上边是边框, 下边是名字, 上边的边框又可以分为上半部分a和下半部分b

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

如何判断武器子弹类别 轻型/重型/能量/狙击/霰弹/空投

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

如何判断武器名称 在确定当前使用的背包的基础上判断

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

先从名字最长的武器开始找点, 从最左边或最右边找, 即可确保该点一定不在别的武器上或在别的武器上该点不是指定的颜色

也可以写一个工具来找点, 原理很简单, 以同样的 left,top,width,height 截取所有武器的名称图, 遍历每一个像素点, 在该点下再遍历所有名称图, 如果只有某张名称图的该点是纯白色, 则记录该点, 可以通过游戏中取该点色, 判断是否纯白, 来唯一确认该武器名称

下面代码是 3440-1440 分辨率下 PUBG 的名称图找点方法, APEX 也类似, 改改 left,top,width,height 就行了

import os
import cv2directory = r'34401440\group'def load(directory):imgs = []names = []for item in os.listdir(directory):path = os.path.join(directory, item)if os.path.isdir(path):t1, t2 = load(path)for temp in t1:imgs.append(temp)for temp in t2:names.append(temp)elif os.path.isfile(path):imgs.append(cv2.imread(path, cv2.IMREAD_COLOR))names.append(item.replace('.png', ''))return imgs, namesimgs, names = load(directory)
print(f'载入{len(imgs)}张图片')left, top, width, height = 2253, 125, 260, 42
exclude = set()
data = []
# 图片尺寸: 260,42
for row in range(0, height):for col in range(0, width):# 轮流遍历每张图片的同一个点, 如果该点只有一张图片是纯白色, 则该点为该图片的识别点, 输入记录# 某点纯白色次数counter = 0temp = -1for i in range(0, len(imgs)):img = imgs[i]name = names[i]if name in exclude:continue(b, g, r) = img[row, col]if (b, g, r) == (255, 255, 255):counter += 1temp = iif counter == 1:name = names[temp]print(f'用于验证的数据:{row},{col}- 截图上的坐标:({col},{row}) - 对应游戏内的点的坐标:({left + col},{top + row}) -{name}')exclude.add(name)data.append((left + col, top + row, name))print(f'data = [')
for item in data:print(f'{item},')
print(f']')

如何判断武器模式 全自动/连发/单发


需要压枪的只有全自动和半自动两种模式的武器, 单发不需要压枪(想把单发武器做成自动连发), 喷子和狙不需要压枪

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

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

如何判断是否持有武器

暂无法判断, 收起武器和持有武器, 没有能明确分辨两种情况的固定点

部分武器可以通过[V]标判断, 因为不全, 先不采用

也可以通过监听按按[3]键(收起武器操作)来设置标记, 其他操作去除标记, 然后通过读取该标记判断是否持有武器, 但不优雅, 先不采用

目前已有的一个特征是, 使用拳头时, 准星是一个大号方形准星, 使用武器时, 都是圆准星. 但是使用拳头不等于未持有武器

如何判断弹夹是否打空


弹夹中子弹数大多为两位数(LSTAR可能为三位数), 所以只需确认十位不为0, 即可认为不空, 十位为0且个位为0, 即可认为空

  • 十位的点, 在数字正中间即可, 1-9都是纯白色, 0是灰色. 注意, 这个灰色不是定色, 该颜色会随着背景改变而改变
  • 个位的点, 在数字0中间斜线的最左端, 这个点是纯白色, 且其他1-9时, 这个点都不是纯白色

何时触发识别

  • 鼠标右键(瞄准模式) 按下, 识别武器. 和游戏内原本的按键功能不冲突
  • 1(1号武器) / 2(2号武器) / 3(收起武器) / E(交互/换枪) / V(切换射击模式) / R(更换弹夹) / Tab(打开背包) / Esc(关闭各种窗口) / Alt(求生物品) 键释放, 识别武器
  • Home 键释放 / 鼠标侧下键按下, 切换开关
  • end 键释放, 结束程序

几个细节点

  • 通过测试发现, 所有武器的发射间隔都大于50毫秒, 所以压枪时, 这50毫秒内可以做一些操作, 比如判断弹夹是否打空, 避免触发压枪

压枪思路

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

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

组织数据

  • 武器数据, 通过子弹类型分组, 组里的每个成员指定序号, 名称, 抖枪参数, 压枪参数等信息
  • 配置数据, 按分辨率分组, 再按是否在游戏中, 是否有武器, 武器位置, 武器子弹类型, 武器索引等信息将配置分类
  • 信号数据, 程序运行时, 进程线程间通讯

具体看 cfg.py 中的 detect, weapon 和 apex.py 中的 init

开发过程

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

找对点就行了

第二阶段实现 能自动采用对应抖枪参数执行压枪

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

能量武器, 专注和哈沃克, 预热和涡轮有很大影响, 这里暂时没管, 在第三阶段实现

第三阶段实现 能自动采用对应压枪参数执行压枪


我的游戏内鼠标设置是这样的, 要确保每个瞄镜的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倍镜偏差最大. 难不成各个镜子做一套参数?

游戏中实测

压枪参数大多都是随便调了下, 并不是很精细, 20米内的表现还行吧, 当然距离一条线还差得远. 专注轻机枪因为射速不固定所以没调

存在的问题

  • 采用取色判断法, 单点取色耗时1-10ms, 性能不足 (已有优化思路, 一种是, 通过GetCurrentObject和GetObject获取和hdc相关的位图对象数据区起始地址, 拿到BitMap, 直接取对应坐标的颜色. 受限于个人水平, 暂无法用Python实现)
  • 检测武器名称使用的是O(n)时间复杂度的遍历方式, 在取色判断法效率低的情况下, 性能不够优秀和稳定, 期望做到O(1)
  • 暂无法判断是否持有武器(有武器但我用拳头, 可能引起错误地触发压枪)
  • 暂无法实现按着左键时模拟左键点击效果, 所以暂无法实现单发枪变连发枪的功能

工程源码

也可以直接拷贝下方代码. 因条件限制, 只适配了 3440*1440 分辨率的游戏数据, 其他分辨率需自行适配

游戏偶尔会调整武器射速, 发现数据不对劲时, 可能需要重新调整压枪参数

GitHub python.apex.weapon.auto.recognize.and.suppress

相关资源

Python Apex 武器自动识别与压枪 全过程记录 百度网盘

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, 64],[1, -2, 10, 64],[1, -2, 10, 64],[1, -4, 10, 64],[1, -6, 10, 64],  #[1, -5, 8, 64],[1, -5, 8, 64],[1, -5, 8, 64],[1, -5, 8, 64],[1, -5, 8, 64],  #[1, 0, 5, 64],[1, 0, 5, 64],[1, -5, 5, 64],[1, -5, 5, 64],[1, -5, 5, 64],  #[1, -5, 3, 64],[1, 0, 3, 64],[1, 0, 3, 64],[1, 0, 3, 64],[1, 0, 3, 64],  #[1, -5, 3, 64],[1, -5, 3, 64],[1, -5, 3, 64],[1, -5, 3, 64],[1, -5, 3, 64],  #]},'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, 10, 100],  #[1, 5, 10, 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],[1, 0, 1, 0]]},},'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,},restrain: [[1, 12, 10, 100],[1, 12, 10, 100],[1, 10, 10, 100],[1, 0, 10, 100],[1, -10, 10, 100],  #[1, -10, 10, 100],[1, -10, 10, 100],[1, 0, 10, 100],[1, 0, 10, 100],[1, 0, 8, 100],[1, 0, 8, 100],[1, 0, 8, 100],[1, 0, 8, 100],[1, 0, 8, 100],  #[1, 0, 8, 100],[1, 0, 8, 100],[1, 0, 8, 100],[1, 0, 8, 100],[1, 0, 8, 100],  #[1, 0, 8, 100],[1, 0, 8, 100],[1, 0, 8, 100],[1, 0, 8, 100],[1, 0, 8, 100],  #[1, 0, 8, 100],[1, 0, 8, 100],[1, 0, 8, 100],[1, 0, 8, 100],[1, 0, 8, 100],  #]},'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 ctypes
import timefrom win32api import GetSystemMetrics
from win32con import SM_CXSCREEN, SM_CYSCREEN, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, DESKTOPHORZRES, DESKTOPVERTRES
from win32print import GetDeviceCaps
from win32gui import GetCursorPos, GetDC, ReleaseDC, GetPixel, GetWindowText, GetForegroundWindow  # conda install pywin32,import cfg
from cfg import detect, weapontry:driver = ctypes.CDLL(r'logitech.dll')  # 在Python的string前面加上‘r’, 是为了告诉编译器这个string是个raw string(原始字符串),不要转义backslash(反斜杠) '\'ok = driver.device_open() == 1if not ok:print('初始化罗技驱动失败, 未安装lgs/ghub驱动')
except FileNotFoundError:print('初始化罗技驱动失败, 缺少文件')class Mouse:@staticmethoddef move(x, y, absolute=False):if ok:if x == 0 and y == 0:returnmx, my = x, yif absolute:ox, oy = GetCursorPos()mx = x - oxmy = y - oydriver.moveR(mx, my, True)@staticmethoddef down(code):if ok:driver.mouse_down(code)@staticmethoddef up(code):if ok:driver.mouse_up(code)@staticmethoddef click(code):""":param code: 1:左键, 2:中键, 3:右键, 4:侧下键, 5:侧上键, 6:DPI键:return:"""if ok:driver.mouse_down(code)driver.mouse_up(code)class Keyboard:@staticmethoddef press(code):if ok:driver.key_down(code)@staticmethoddef release(code):if ok:driver.key_up(code)@staticmethoddef 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:"""显示器"""@staticmethoddef pixel(x, y):"""效率很低且不稳定, 单点检测都要耗时1-10ms获取颜色, COLORREF 格式, 0x00FFFFFF结果是int,可以通过 print(hex(color)) 查看十六进制值可以通过 print(color == 0x00FFFFFF) 进行颜色判断"""hdc = GetDC(None)color = GetPixel(hdc, x, y)ReleaseDC(None, hdc)  # 一定要释放DC, 不然随着该函数调用次数增加会越来越卡, 表现就是不调用该函数, 系统会每两秒卡一下, 调用次数越多, 卡的程度越厉害return color@staticmethoddef resolution():"""显示分辨率"""w = GetSystemMetrics(SM_CXSCREEN)h = GetSystemMetrics(SM_CYSCREEN)return w, hclass Game:"""游戏工具"""@staticmethoddef key():w, h = Monitor.resolution()return f'{w}:{h}'@staticmethoddef game():"""是否游戏窗体在最前"""return 'Apex Legends' == GetWindowText(GetForegroundWindow())@staticmethoddef 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 Falsereturn True@staticmethoddef 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, Noneelse:bi = data.get(hex(color))return (1, bi) if color == Monitor.pixel(x, y + 1) else (2, bi)@staticmethoddef 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 + 1elif 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 + 1return None@staticmethoddef 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 1x, y = data.get('2')if color == Monitor.pixel(x, y):return 2return None@staticmethoddef armed():"""是否持有武器"""return True@staticmethoddef 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 Falsex, y = data.get('2')return color == Monitor.pixel(x, y)@staticmethoddef 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, Nonediffer = data.get(cfg.differ)data = data.get(str(wi))if data is None:return False, Nonex, y = dataresult = color == Monitor.pixel(x, y)return (True, differ) if result else (False, None)@staticmethoddef 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, Nonediffer = data.get(cfg.differ)data = data.get(str(wi))if data is None:return False, Nonex, y = dataresult = color == Monitor.pixel(x, y)return (True, differ) if result else (False, None)@staticmethoddef 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, 开关已关闭')returnif Game.game() is False:data[cfg.shake] = Nonedata[cfg.restrain] = Nonet2 = time.perf_counter_ns()print(f'耗时:{t2 - t1}ns, 约{(t2 - t1) // 1000000}ms, 不在游戏中')returnif Game.play() is False:data[cfg.shake] = Nonedata[cfg.restrain] = Nonet2 = time.perf_counter_ns()print(f'耗时:{t2 - t1}ns, 约{(t2 - t1) // 1000000}ms, 不在游戏中')returnpi, bi = Game.index()if (pi is None) | (bi is None):data[cfg.shake] = Nonedata[cfg.restrain] = Nonet2 = 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, 不是自动/半自动武器')#     returnwi = Game.weapon(pi, bi)if wi is None:data[cfg.shake] = Nonedata[cfg.restrain] = Nonet2 = 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 Processimport pynput  # pip 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, 其他进程线程在感知到变更后结束自身switch: True,  # 检测和压枪开关fire: False,  # 开火状态shake: None,  # 抖枪参数restrain: None,  # 压枪参数
}def mouse(data):def down(x, y, button, pressed):if button == pynput.mouse.Button.right:if pressed:Game.detect(data)elif button == pynput.mouse.Button.left:data[fire] = pressedelif button == pynput.mouse.Button.x1:if pressed:data[switch] = not data.get(switch)with pynput.mouse.Listener(on_click=down) as m:m.join()def keyboard(data):def release(key):if key == pynput.keyboard.Key.end:# 结束程序data[end] = Truereturn Falseelif 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)with pynput.keyboard.Listener(on_release=release) as k:k.join()def suppress(data):while True:if data.get(end):breakif data.get(switch) is False:continueif data.get(fire):if data.get(restrain) is not None:for item in data.get(restrain):if not data.get(fire):  # 停止开火breakt1 = time.perf_counter_ns()if not Game.game():  # 不在游戏中breakif not Game.armed():  # 未持有武器breakif Game.empty():  # 弹夹为空breakt2 = time.perf_counter_ns()# operation: # 1:移动 2:按下operation = item[0]if operation == 1:temp, x, y, delay = itemMouse.move(x, y)delay = (delay - (t2 - t1) // 1000 // 1000) / 1000if delay > 0:time.sleep(delay)elif operation == 2:temp, code, delay = itemMouse.click(code)delay = (delay - (t2 - t1) // 1000 // 1000) / 1000if delay > 0:time.sleep(delay)elif data.get(shake) is not None:total = 0  # 总计时 msdelay = 1  # 延迟 mspixel = 4  # 抖动像素while True:if not data[fire]:  # 停止开火breakif not Game.game():  # 不在游戏中breakif not Game.armed():  # 未持有武器breakif Game.empty():  # 弹夹为空breakt = time.perf_counter_ns()if total < data[shake][speed] * data[shake][count]:Mouse.move(0, data[shake][strength])time.sleep(delay / 1000)total += delayelse:Mouse.move(0, 1)time.sleep(delay / 1000)total += delay# 抖枪Mouse.move(pixel, 0)time.sleep(delay / 1000)total += delayMouse.move(-pixel, 0)time.sleep(delay / 1000)total += delaytotal += (time.perf_counter_ns() - t) // 1000 // 1000if __name__ == '__main__':multiprocessing.freeze_support()  # windows 平台使用 multiprocessing 必须在 main 中第一行写这个manager = multiprocessing.Manager()data = manager.dict()  # 创建进程安全的共享变量data.update(init)  # 将初始数据导入到共享变量# 将键鼠监听和压枪放到单独进程中跑pm = Process(target=mouse, args=(data,))pk = Process(target=keyboard, args=(data,))ps = Process(target=suppress, args=(data,))pm.start()pk.start()ps.start()pk.join()  # 不写 join 的话, 使用 dict 的地方就会报错 conn = self._tls.connection, AttributeError: 'ForkAwareLocal' object has no attribute 'connection'pm.terminate()  # 鼠标进程无法主动监听到终止信号, 所以需强制结束

打包与使用

Anaconda Prompt (miniconda) 中先激活对应的虚拟环境, 然后切到工程目录, 执行

pip install pyinstaller
# 主要文件是 apex.py, 将以 dist/apex 作为输出目录
pyinstaller apex.py -p cfg.py -p toolkit.py -p mouse.device.lgs.dll

在工程下会多出来一个 dist/apex 目录, 里面就是打包好的程序和相关依赖了, 将整个文件夹打包成压缩包即可分享(暂未在其他电脑上验证)

(base) C:\Users\mrathena>conda activate apex(apex) C:\Users\mrathena>pip install pyinstaller(apex) C:\Users\mrathena>cd C:\mrathena\develop\workspace\pycharm\python.apex.helper(apex) C:\mrathena\develop\workspace\pycharm\python.apex.helper>pyinstaller apex.py -p cfg.py -p toolkit.py -p mouse.device.lgs.dll
889 INFO: PyInstaller: 5.4.1
889 INFO: Python: 3.9.13 (conda)
899 INFO: Platform: Windows-10-10.0.22621-SP0
900 INFO: wrote C:\mrathena\develop\workspace\pycharm\python.apex.helper\apex.spec
903 INFO: UPX is not available.
905 INFO: Extending PYTHONPATH with paths
['C:\\mrathena\\develop\\workspace\\pycharm\\python.apex.helper','C:\\mrathena\\develop\\workspace\\pycharm\\python.apex.helper\\cfg.py','C:\\mrathena\\develop\\workspace\\pycharm\\python.apex.helper\\toolkit.py','C:\\mrathena\\develop\\workspace\\pycharm\\python.apex.helper\\mouse.device.lgs.dll']
1624 INFO: checking Analysis
1624 INFO: Building Analysis because Analysis-00.toc is non existent
1625 INFO: Initializing module dependency graph...
1629 INFO: Caching module graph hooks...
1640 WARNING: Several hooks defined for module 'numpy'. Please take care they do not conflict.
1646 INFO: Analyzing base_library.zip ...
5586 INFO: Loading module hook 'hook-encodings.py' from 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\lib\\site-packages\\PyInstaller\\hooks'...
7785 INFO: Loading module hook 'hook-pickle.py' from 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\lib\\site-packages\\PyInstaller\\hooks'...
8553 INFO: Loading module hook 'hook-heapq.py' from 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\lib\\site-packages\\PyInstaller\\hooks'...
9233 INFO: Caching module dependency graph...
9449 INFO: running Analysis Analysis-00.toc
9469 INFO: Adding Microsoft.Windows.Common-Controls to dependent assemblies of final executablerequired by C:\mrathena\develop\miniconda\envs\apex\python.exe
9704 INFO: Analyzing C:\mrathena\develop\workspace\pycharm\python.apex.helper\apex.py
9767 INFO: Loading module hook 'hook-multiprocessing.util.py' from 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\lib\\site-packages\\PyInstaller\\hooks'...
9924 INFO: Loading module hook 'hook-xml.py' from 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\lib\\site-packages\\PyInstaller\\hooks'...
10394 INFO: Loading module hook 'hook-pynput.py' from 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\lib\\site-packages\\_pyinstaller_hooks_contrib\\hooks\\stdhooks'...
11733 INFO: Processing pre-safe import module hook six.moves from 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\lib\\site-packages\\PyInstaller\\hooks\\pre_safe_import_module\\hook-six.moves.py'.
11889 INFO: Loading module hook 'hook-platform.py' from 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\lib\\site-packages\\PyInstaller\\hooks'...
12003 INFO: Processing module hooks...
12085 INFO: Loading module hook 'hook-Xlib.py' from 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\lib\\site-packages\\_pyinstaller_hooks_contrib\\hooks\\stdhooks'...
14018 INFO: Looking for ctypes DLLs
14043 INFO: Analyzing run-time hooks ...
14046 INFO: Including run-time hook 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_subprocess.py'
14048 INFO: Including run-time hook 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_multiprocessing.py'
14050 INFO: Including run-time hook 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_pkgutil.py'
14054 INFO: Including run-time hook 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py'
14061 INFO: Looking for dynamic libraries
917 INFO: Extra DLL search directories (AddDllDirectory): []
917 INFO: Extra DLL search directories (PATH): ['C:\\mrathena\\develop\\miniconda\\envs\\apex', 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\Library\\mingw-w64\\bin', 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\Library\\usr\\bin', 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\Library\\bin', 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\Scripts', 'C:\\mrathena\\develop\\miniconda\\envs\\apex\\bin', 'C:\\mrathena\\develop\\miniconda\\condabin', 'C:\\WINDOWS\\system32', 'C:\\WINDOWS', 'C:\\WINDOWS\\System32\\Wbem', 'C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0', 'C:\\WINDOWS\\System32\\OpenSSH', 'C:\\Program Files\\dotnet', 'C:\\mrathena\\develop\\xftp', 'C:\\mrathena\\develop\\tortoise.git\\bin', 'C:\\Program Files (x86)\\Common Files\\Thunder Network\\KanKan\\Codecs', 'C:\\Program Files\\NVIDIA Corporation\\NVIDIA NvDLISR', 'C:\\Program Files (x86)\\NVIDIA Corporation\\PhysX\\Common', 'C:\\mrathena\\develop\\xshell', 'C:\\mrathena\\application\\mpv.player', '.', 'C:\\WINDOWS\\system32', 'C:\\WINDOWS', 'C:\\WINDOWS\\System32\\Wbem', 'C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0', 'C:\\WINDOWS\\System32\\OpenSSH', 'C:\\mrathena\\develop\\miniconda', 'C:\\mrathena\\develop\\miniconda\\Library\\mingw-w64\\bin', 'C:\\mrathena\\develop\\miniconda\\Library\\usr\\bin', 'C:\\mrathena\\develop\\miniconda\\Library\\bin', 'C:\\mrathena\\develop\\miniconda\\Scripts', 'C:\\mrathena\\develop\\python-3.10.7\\Scripts', 'C:\\mrathena\\develop\\python-3.10.7', 'C:\\Users\\mrathena\\AppData\\Local\\Microsoft\\WindowsApps', 'C:\\mrathena\\application\\bandizip', 'C:\\mrathena\\develop\\jdk-17.0.3.1\\bin', 'C:\\mrathena\\develop\\apache-maven-3.6.3\\bin', 'C:\\mrathena\\develop\\portable.git\\bin', 'C:\\Users\\mrathena\\.dotnet\\tools', 'C:\\mrathena\\develop\\fiddler', 'C:\\mrathena\\application\\mpv.player', 'C:\\mrathena\\develop\\vapour-synth-r59', 'C:\\mrathena\\develop\\vapour-synth-r59\\core', 'C:\\mrathena\\develop\\vapour-synth-r59\\vsrepo', 'C:\\Users\\mrathena\\.dotnet\\tools', 'C:\\Users\\mrathena\\AppData\\Local\\Microsoft\\WindowsApps', '.']
15680 INFO: Looking for eggs
15680 INFO: Using Python library C:\mrathena\develop\miniconda\envs\apex\python39.dll
15681 INFO: Found binding redirects:
[]
15708 INFO: Warnings written to C:\mrathena\develop\workspace\pycharm\python.apex.helper\build\apex\warn-apex.txt
15754 INFO: Graph cross-reference written to C:\mrathena\develop\workspace\pycharm\python.apex.helper\build\apex\xref-apex.html
15782 INFO: checking PYZ
15782 INFO: Building PYZ because PYZ-00.toc is non existent
15783 INFO: Building PYZ (ZlibArchive) C:\mrathena\develop\workspace\pycharm\python.apex.helper\build\apex\PYZ-00.pyz
16420 INFO: Building PYZ (ZlibArchive) C:\mrathena\develop\workspace\pycharm\python.apex.helper\build\apex\PYZ-00.pyz completed successfully.
16433 INFO: checking PKG
16433 INFO: Building PKG because PKG-00.toc is non existent
16433 INFO: Building PKG (CArchive) apex.pkg
16446 INFO: Building PKG (CArchive) apex.pkg completed successfully.
16448 INFO: Bootloader C:\mrathena\develop\miniconda\envs\apex\lib\site-packages\PyInstaller\bootloader\Windows-64bit\run.exe
16448 INFO: checking EXE
16448 INFO: Building EXE because EXE-00.toc is non existent
16449 INFO: Building EXE from EXE-00.toc
16450 INFO: Copying bootloader EXE to C:\mrathena\develop\workspace\pycharm\python.apex.helper\build\apex\apex.exe.notanexecutable
16489 INFO: Copying icon to EXE
16489 INFO: Copying icons from ['C:\\mrathena\\develop\\miniconda\\envs\\apex\\lib\\site-packages\\PyInstaller\\bootloader\\images\\icon-console.ico']
16491 INFO: Writing RT_GROUP_ICON 0 resource with 104 bytes
16491 INFO: Writing RT_ICON 1 resource with 3752 bytes
16491 INFO: Writing RT_ICON 2 resource with 2216 bytes
16491 INFO: Writing RT_ICON 3 resource with 1384 bytes
16492 INFO: Writing RT_ICON 4 resource with 37019 bytes
16493 INFO: Writing RT_ICON 5 resource with 9640 bytes
16493 INFO: Writing RT_ICON 6 resource with 4264 bytes
16494 INFO: Writing RT_ICON 7 resource with 1128 bytes
16530 INFO: Copying 0 resources to EXE
16531 INFO: Embedding manifest in EXE
16532 INFO: Updating manifest in C:\mrathena\develop\workspace\pycharm\python.apex.helper\build\apex\apex.exe.notanexecutable
16534 INFO: Updating resource type 24 name 1 language 0
16570 INFO: Appending PKG archive to EXE
16573 INFO: Fixing EXE headers
16732 INFO: Building EXE from EXE-00.toc completed successfully.
16735 INFO: checking COLLECT
16735 INFO: Building COLLECT because COLLECT-00.toc is non existent
16736 INFO: Building COLLECT COLLECT-00.toc
17544 INFO: Building COLLECT COLLECT-00.toc completed successfully.(apex) C:\mrathena\develop\workspace\pycharm\python.apex.helper>

拓展 目标检测 与 自瞄, 彻底告别压枪

Python Apex Legends YOLO v5 AI 自瞄 全过程记录

不同的游戏, 都需要准备大量数据集做训练, 才能取得比较好的效果

拓展 通用型人体骨骼检测 与 自瞄, 训练一次, FPS 游戏通用

【亦】警惕AI外挂!我写了一个枪枪爆头的视觉AI,又亲手“杀死”了它

YOLO V7 keypoint 人体关键点检测

大多数 FPS 游戏中要检测的目标都为人形, 可以训练一个 通用型人体骨骼检测模型, 在各种游戏中都能起到不错的效果

Python Apex 武器自动识别与压枪 全过程记录相关推荐

  1. Python Apex YOLO V7 main 目标检测 全过程记录

    博文目录 文章目录 环境准备 YOLO V7 main 分支 TensorRT 环境 工程源码 假人权重文件 toolkit.py 测试.实时检测.py grab.for.apex.py label. ...

  2. linux 配置tensorflow 全过程记录

    linux 配置tensorflow 全过程记录 前几天刚下一个deepin系统,是基于linux 内核的,界面的设计有些mac的feel 感觉还是挺不错的,之后就赶紧配置了一下tensorflow ...

  3. 使用Java读取 “Python写入redis” 的数据踩坑记录

    https://my.oschina.net/u/2338224/blog/3061507 使用Java读取 "Python写入redis" 的数据踩坑记录 https://seg ...

  4. python记录日志_5分钟内解释日志记录—使用Python演练

    python记录日志 Making your code production-ready is not an easy task. There are so many things to consid ...

  5. 《Python编程从入门到实践》记录之json模块(数据存储)

    模块json让你能够将简单的Python数据结构转储到文件中, 并在程序再次运行时加载该文件中的数据. 你还可以使用json在Python程序之间分享数据.更重要的是,JSON数据格式并非Python ...

  6. 《Python编程从入门到实践》记录之Python函数返回值

    目录 1.返回简单值 2.返回字典 3.结合使用函数和while循环 更多关于Python函数的知识见如下博文: <Python编程从入门到实践>记录之Python函数定义.使用 < ...

  7. 《Python编程从入门到实践》记录之第7章 用户输入(input)和while 循环总结(思维导图)

    有关input函数可参考博文<Python编程从入门到实践>记录之input()函数的详细介绍. 有关while循环可参考博文<Python编程从入门到实践>记录之while循 ...

  8. 《Python编程从入门到实践》记录之while循环简介(break、continue)

    目录 1.使用while循环让用户选择何时退出 2.使用break退出循环 3.使用continue返回循环开头 4.while循环处理列表和字典--在列表之间移动元素 5.while循环处理列表和字 ...

  9. 《Python编程从入门到实践》记录之求模运算符

    处理数值信息时, 求模运算符(%)是一个很有用的工具,它将两个数相除并返回余数. 如果一个数可被另一个数整除,余数就为0,因此求模运算符将返回0. 可利用这一点来判断一个数是奇数还是偶数: #!/us ...

  10. java手机验证码登陆_在Web项目中手机短信验证码实现的全过程记录

    这篇文章主要给大家介绍了关于在Web项目中实现短信验证码的全过程记录,文中通过示例代码介绍的非常详细,在文末跟大家提供了源码下载,需要的朋友可以参考借鉴,下面随着小编来一起学习学习吧. 前言 最近在做 ...

最新文章

  1. 联想笔记本Win10 F1-F12失效的解决方法
  2. 字符统计2_JAVA
  3. Java中Connection方法笔记
  4. HDU - 6156 Palindrome Function(数位dp)
  5. Go语言JSON与Byte[]转化
  6. (七)React使用
  7. win7窗口颜色没有透明的开启教程
  8. 图解 RoIAlign 以及在 PyTorch 中的使用(含代码示例)
  9. 拖动时候的样式怎么改_你对“挡拆”的死板印象是时候要改了!看看欧文、保罗都是怎么做的吧。...
  10. Entity Framework之问题收集
  11. JavaScript强化教程——AngularJS 表达式
  12. 证券交易1-交易系统简介
  13. 安防弱电智能化VISIO图标图例(几百个),让你的技术方案瞬间高大上起来
  14. mysql 启动 配置文件,mysql启动服务配置文件编写
  15. RestClient操作文档
  16. 非线性光纤光学——光孤子
  17. 综合项目之闪讯破解(四)之 如何用C++编写可被C#调用的Dll
  18. 分析android图片的抖动处理
  19. Linux终端更改字体
  20. Scope参数错误或没有Scope权限

热门文章

  1. 现代语音信号处理之语音信号的非线性分析
  2. python里lambda和filter和map的用法_python中lambda以及与filter/map/reduce结合的用法
  3. idea 修改前后端代码自动运行
  4. ubuntu16使用labelImg
  5. ubuntu 安装 ftp server
  6. 【持续更新】java 指令释疑
  7. Token 的生成 和 验证
  8. 有意思的堪称世界级的电视广告!!:)
  9. linux redis集群工具,Redis集群部署及常用的操作命令
  10. Altium Designer 15 PCB图层详解