文章目录
环境准备
操纵键鼠
驱动安装 链接库加载 代码准备和游戏外测试
toolkit.py
游戏内测试
键鼠监听
武器识别
如何简单且高效判断是否在游戏内
如何简单且高效判断背包状态 无武器/1号武器/2号武器
如何简单且高效判断武器子弹类别
如何简单且高效判断武器名称
如何简单且高效判断武器模式 全自动/连发/单发
何时触发识别
压枪思路
组织数据
第一阶段实现 能自动识别出所有武器
cfg.py
toolkit.py
apex.py
第二阶段实现 能自动识别出所有武器并采用对应压枪参数执行压枪
第三阶段实现 放弃抖枪术 转常规后座抵消法
本文为下面参考文章的学习与实践
[原文] FPS游戏自动枪械识别 压枪(以PUBG为例)
环境准备
python Windows 开发环境搭建
说明
基础环境
从 python 官网下载安装包并安装, 配置环境变量后, 在命令行内可以执行 python 命令
包管理工具pip: 一个现代的,通用的 Python 包管理工具。提供了对 Python 包的查找、下载、安装、卸载的功能。注:pip 已内置于 Python 3.4 和 2.7 及以上版本,其他版本需另行安装。虚拟环境
python 基础环境下, 不同的依赖只能存在一个版本, 而不同的项目可能依赖了同一个包的不同版本, 这样的项目就可能无法在同一个 python 基础环境下运行. 基于基础环境创建的虚拟环境是相互隔离的, 第三方依赖包可根据项目要求自行下载, 不同项目运行在不同的虚拟环境几下就可以避免以来冲突等问题
虚拟环境只能基于本地存在的基础环境来创建, 会继承基础环境自带的库, 可以选择是否继承基础环境的已安装的第三方包
我觉得可以借鉴学习 java maven 的依赖管理理念, 告别虚拟环境
虚拟环境管理工具virtualenv: virtualenv可以为每个项目创建一套隔离的Python环境, 再使用pip进行包管理venv: python 3.3 起自带的虚拟环境管理工具pipenv:virtualenvwrapper:virtualenvwrapper-win:virtualenv-burrito:autoenv:pyvenv:pyenv:Conda
CondaCondaMinicondaAnaconda
Conda 是一个开源的 环境和包管理系统, 它可以创建并管理完全隔离的不同版本的 python 环境, 也可以创建并管理某 python 版本的完全隔离的虚拟环境, 用了它就不必再安装基础环境了
默认配置下, Conda 可以安装和管理由 Anaconda® 构建、审查和维护的数千个包。版本通常低于最新版
Anaconda: Anaconda是一个打包的集合,里面预装好了 Conda、Python、众多数据科学和机器学习相关的包、科学计算工具等等,所以也称为Python的一种发行版。Miniconda: Miniconda 是一个免费的 conda 最小安装程序。 它是 Anaconda 的一个小型引导版本,仅包含 Conda、Python、它们所依赖的包以及少量其他有用的包,包括 pip、zlib 和其他一些包。Anaconda Navigator: Anaconda 的 GUI 管理工具基础环境搭建
Python 官网Python Windows 下载
到官网找到 Windows 最新版下载并安装
pip 是 Python 包管理工具,该工具提供了对Python 包的查找、下载、安装、卸载的功能。
什么是 Python Launcher?
python 安装程序会自动在 path 环境变量中添加这两条目录
目录结构说明
vc dll 结构体_python的安装目录结构
python.exe: python 解释器, 运行时会弹出控制台窗口pythonw.exe: 无窗口的python可执行程序, 代码在后台运行DLLs: 包含 python 的 *.pyd(Python动态模块)文件与几个Windows的 *.dll(动态链接库)文件pyd 文件是由 D 语言编写的一种 dll 文件, 可以保护 python 文件的源码不被暴露Doc: 帮助文档include: python 的 C 语言接口头文件(.h结尾), 当在 C 程序中集成 python 时, 会用到这个目录下的头文件C语言中, 后缀为 .h 的文件是头文件, 内含函数声明、宏定义、结构体定义等内容。 后缀为 .c 的文件是源文件, 内含函数实现,变量定义等内容。 为什么要有头文件? C/C 编译的时候先扫描整个文件有没有语法错误, 然后将C语句转化为汇编, 当碰到不认识的变量、类、函数、对象的命名时, 首先查找它有没有声明, 如果没有声明直接报错, 如果有,则根据对应的定义空出一定的存储空间并进行相关的指令转化。Lib: python 自带的标准库/包/测试套件等Lib/site-packages: 存放安装的第三方库, pip install 安装的第三方库就放在这里libs: python 的 C 语言接口库文件Scripts: 脚本文件, 如 pip.exe 包管理器等tcl: python 与 TCL 的结合Tools: 一些工具Miniconda 环境搭建
Miniconda
红字提示, 不推荐勾选添加环境变量, 因为可能会导致因路径被添加到靠前的位置而造成问题. 如果是首次安装 python 相关环境, 可以选择添加到环境变量选项, 如果已经有在用的其他配置了 PATH 的 Conda 或者 Python 则不建议
确实在用户环境变量 PATH 里加了很多目录, 查看这些目录下都有哪些 exe, 根目录下有 python.exe
使用方式
安装完成后, 从开始菜单中找到并打开 [Anaconda Prompt], 运行 [conda list] 命令, 如果正确安装, 则会出现已安装的包列表
常用命令
Command referenceconda常用命令:安装,更新,创建,激活,关闭,查看,卸载,删除,清理,重命名,换源,问题Anaconda /Miniconda 常用命令CONDA集合
查看帮助
conda -hconda –helpconda install -hconda install –helpconda env -h12345查看信息
conda info # 包含 conda, python, pip 等, 还有当前在 conda 命令行中激活的环境1
列出环境
conda env listconda info -e12
新安装的 Conda 只有 base 基础环境, 没有虚拟环境
配置源
windows环境下conda更换为国内清华镜像源
编辑用户目录下的 .condarc 文件即可更换 conda 默认源。
# Windows 用户无法直接创建名为 .condarc 的文件,需要先执行如下命令,生成该文件后再修改。C:Users用户名.condarc# 设置搜索时显示通道地址conda config –set show_channel_urls yes123
修改文件内容
channels: – https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ – https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ – https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/menpo/ – https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/bioconda/ – https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/msys2/ – https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/ – https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/ – defaultsshow_channel_urls: true12345678910
运行 conda clean -i 清除索引缓存,保证用的是镜像站提供的索引。
运行 conda config –show 确认源信息
虚拟环境
# 创建虚拟环境conda create -hconda create -n testenvconda create -n testenv2 python=3.8conda create -n testenv3 python=3.10.7 # 貌似不能下 Anaconda 库中没有的 python 版本, 表现就是转圈很久conda create -p C:mrathenadevelopworkspacepycharmyolov5-6.2venv# 查看环境包conda list # 查看当前激活环境的包, 默认激活的是 base 基础环境conda list -n testenv # 查看指定虚拟环境的包# 激活虚拟环境conda activate testenvconda activate C:mrathenadevelopworkspacePyCharmyolov5-6.2venv# 反激活conda deactivate # 退出虚拟环境, 重新激活 base 基础环境# 删除虚拟环境conda remove -n testenv –allconda remove -p C:mrathenadevelopworkspacepycharmyolov5-6.2venv –all1234567891011121314151617
如果报错如下, 检查是否有开代理工具, 关闭代理, 重开工具就可以了
CondaHTTPError: HTTP 000 CONNECTION FAILED for url 1创建虚拟环境的注意点
创建虚拟环境时, 一定要指定一个不同于 base python 版本的 python 版本
不然的话, 新的虚拟环境基本等同于没有创建, 使用的仍然是 base 环境, 执行 pip install 会污染 base 环境, 真是恶心
创建了一个不同于 base python 版本的虚拟环境后, 在虚拟环境中会实打实包含类似 base 的目录结构, 也包含对应的 pip.exe, 这时候再执行 pip install 就不会影响到 base 环境了
IDE PyCharm
pycharm的virtualenv、pipenv、conda详解
下载最新版如 pycharm-professional-2021.2.3.exe
以下选项自选
Create Desktop Shortcut, 64-bit launcher, 创建64位启动器的桌面快捷方式, 非常建议Update context menu, Add “Open Folder as Project”, 在上下文菜单(文件夹右键)添加\”以项目的方式打开该文件夹\”选项, 可选Create Associations, .py, 创建 .py 文件的关联, 默认使用 PyCharm 打开 .py 文件, 非常建议Download and install JRE x86 by JetBrains, 下载 JRE? 不确定做什么, 不选Update PATH variable(restart needed), Add “bin” folder to the PATH, 更新 PATH 环境变量, 将启动器目录添加到 PATH, 不选
创建工程时, 建议每个工程都创建新的虚拟环境, 通过 Conda
测试
在 conda 命令行中也能看到 pycharm 中创建的虚拟环境, 但是没有名字
插件
Chinese (Simplified) Language Pack / 中文语言包
寻找文档
Pypi
在官网输入包名, 找到包, 点进去, 里面一般都会有项目说明, GitHub, 文档等内容
conda create -n apex python=3.91操纵键鼠
由于绝地求生屏蔽了硬件驱动外的其他鼠标输入,因此我们无法直接通过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(\’缺少文件\’)123456789
装了该驱动后, 无需重启电脑, 当下就生效了. 遗憾的是, 没有对应的文档, 只能猜测参数了
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)12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970游戏内测试
在游戏里面试过后, 管用, 但是不准, 猜测可能和游戏内鼠标灵敏度/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()123456789101112键鼠监听
前面说到,要实现压枪就要对各种配件、状态做出识别。那么在写识别的函数之前,我们先要解决的是何时识别的问题。如果识别使用多线程多进程的一直持续检测,无疑是一种巨大的开销,因此就需要对键盘、鼠标的状态进行监听。只有按下特定按键时,才触发特定相应的识别请求。
这里我使用的钩子是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()1234567891011121314
Listener中绑定on_press和on_release的函数( on_key_press、on_key_release),它们返回False的时候是结束监听,下文鼠标监听的函数同理,所以不要随便返回False
键盘的特殊按键采用keyboard.Key.tab这种写法,普通按键用keyboard.KeyCode.from_char(‘c’)这种写法
这里有一点非常坑,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 释放, 鼠标 右键 按下, 这个如果不影响开枪就这个了, 影响的话就改成侧下键. 键位和键在游戏内的功能不冲突的
压枪思路
apex 的压枪有两个思路, 因为 apex 不同武器的弹道貌似是固定的, 其他游戏也是??
左右抖动抵消水平后坐力, 下拉抵消垂直后坐力. 这种方法简单, 但是画面会抖动, 效果也不是很好根据武器配件等测试不同情况下的武器后坐力数据, 然后做反向抵消.可以通过取巧的方式, 只做无配件状态下的反向抵消, 还省了找配件的麻烦这种方法太难太麻烦了, 但是做的好的话, 基本一条线, 强的离谱
我先试试 抖枪大法
组织数据
武器数据, 通过子弹类型分组, 组里的每个成员指定序号, 名称, 压枪参数等信息
配置数据, 按分辨率分组, 再按是否在游戏中, 是否有武器, 武器位置, 武器子弹类型, 武器索引等信息分类
信号数据, 程序运行时, 进程线程间通讯
第一阶段实现 能自动识别出所有武器
目前测试下来, 一波识别大概六七十毫秒的样子, 最多也不超过一百毫秒, 主要耗时在取色函数(1-10ms), 性能已经够用了
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 } } }, \”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: \’暴走\’, }, }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206toolkit.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 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)123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277apex.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()123456789101112131415161718192021222324252627282930313233343536373839404142434445
第二阶段实现 能自动识别出所有武器并采用对应压枪参数执行压枪第三阶段实现 放弃抖枪术 转常规后座抵消法
1