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

news2024/11/27 15:33:17

博文目录

文章目录

  • 环境准备
  • 压枪原理
  • 需求分析
    • 求两张图片的相似度
    • 背包检测 是否在背包界面
    • 武器识别
      • 名称识别 纯白计数法
      • 配件识别 瞄具/枪口/握把/枪托 相似对比法
    • 模式识别 全自动/半自动/单发
    • 姿态识别 站/蹲/爬
    • 余弹识别
    • 激活识别 是否持有武器/一号武器/二号武器 (未完成, 做不下去了)
  • 压枪数据
  • 工程源码
    • 相关资源
    • GitHub
    • cfg.py
    • structure.py
    • toolkit.py
    • pubg.py
    • 分析测试等源码见 GitHub 工程


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

环境准备

参考 该文章 中的 环境准备 部分

压枪原理

在开枪后, 武器准星因后坐力会向上跳起一定幅度, 在下一发子弹射出前, 通过代码控制鼠标下拉, 促使武器准星快速回归原位, 这就是压枪

Pubg 的压枪主要针对的是垂直方向后坐力, 这个后坐力是相当的大, 不同武器配件不同射击姿态对该后坐力也有一定的影响

假设裸配武器的垂直后坐力是固定的, 可以通过执行一组鼠标下移让裸配武器在站立射击时准星基本保持在一条水平线上, 这组鼠标下移的距离就是该武器的基础下压数据

当装备影响垂直后坐力的配件或射击姿态发生改变, 这组下移距离也得跟着改变才能继续保持一条水平线

最终的下移距离=基础下移距离×瞄具影响倍数×枪口影响倍数×握把影响倍数×枪托影响倍数×姿态影响倍数

需求分析

求两张图片的相似度

有两张图片, 如果长宽相同, 相同位置点的颜色相同, 即可认为两张图片完全相同, 其相似度为 1

求相似度之前, 通常我们要对图片做一些处理, 如灰度化/自适应二值化/消除孤立点等, 以突出主体特征, 消除背景干扰

简单实现求两张图片相似度的思路如下

  • 定义相似度的取值范围是 [0, 1], 0 表示完全不同, 1 表示完全相同
  • 如果两张图片宽高不同, 通道数不同, 认为其相似度为 0
  • 遍历每一个点, 记录相同颜色的点的个数, 相同色点数与总点数的比值, 可近似认为是两张图片的相似度(有缺陷)
  • 分块统计相似度, 给块相似度做立方计算, 以扩大块相似度的影响, 立方后的块相似度之和与总块数的比值, 可近似认为是两张图片的最终相似度

通过测试, 效果相当好, 即使是最难看出区别的步枪消音和狙击枪消音都能完美识别

背包检测 是否在背包界面

预先截取背包界面顶部偏左 [背包] 两个字作为基准图, 检测时截取同一位置图与基准图求相似度, 相似度高于 0.9 即可认为当前在背包界面

按下 Tab 键如果立即截图, 则可能背包还没有打开, 而且 Tab 键同时控制背包打开与关闭, 所以不能做成按下 Tab 键, 延迟截图识别

这里可以采用状态机的方式, 定义下面 4 种状态, Tab 键按下触发状态变更, 然后做不同的操作

  • 0: 等待打开背包
  • 1: 背包检测中
  • 2: 武器识别中
  • 3: 武器已识别, 等待关闭背包

常规识别流程大概如下

  • 状态是 0
  • 按下 Tab 键(打开背包), 检测到当前状态是 0, 触发状态变更为 1, 开始检测背包是否成功打开
    • 如果在检测背包过程中按下了 Tab 键(关闭背包), 直接置状态为 0, 背包检测过后不再继续识别武器
  • 当背包成功打开后, 判断状态是否还是 1, 是的话触发状态变更为 2, 开始识别武器, 识别成功后会将武器数据存到内存中
    • 如果在识别武器过程中按下了 Tab 键(关闭背包), 直接置状态为 0, 武器识别过后不保存数据, 不再等待关闭背包
  • 当武器成功识别后, 判断状态是否还是 2, 是的话触发状态变更为 3, 等待关闭背包
  • 按下 Tab 键(关闭背包), 检测到当前状态是 3, 触发状态还原为 0, 等待下一轮识别流程的启动

介于各种原因, 可能打开关闭背包与状态不对应, 所以需要设置一个自动纠错机制, 单次检测背包最多循环 10 次, 超过则放弃操作, 还原状态 0

在这里插入图片描述
在这里插入图片描述

武器识别

检测到在背包界面, 则截取右边两把主武器的部分作为截图, 然后切割武器名称和武器配件部分, 分别识别

在这里插入图片描述

名称识别 纯白计数法

先截相同大小的所有武器的名称图作为基准图, 统计其中纯白色点的个数(通常不会有重复的), 做成字典, 数量为 Key, 名称为 Value

识别时, 从武器大图中切割出武器名称部分, 数纯白点个数, 到字典中直接取出对应名称, 耗时低, 时间复杂度为 O(1)

如果出现两把武器的纯白点个数相同的情况, 只需在对应武器名称上找一个纯白点, 确保该点在其他重复武器上不是纯白, 以此区分

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

配件识别 瞄具/枪口/握把/枪托 相似对比法

先截武器不同部位的所有配件图作为基准图, 按配件类型分类

识别时, 从武器大图中切割出配件图, 遍历同类配件的基准图并与之对比求相似度, 相似度超过 0.9 的即可认为识别成功

瞄具稍有不同, 基准图和切割图只需要瞄具方框的上面一半即可, 因为下半部分会受不同武器的背景影响

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

模式识别 全自动/半自动/单发

射击模式只关心突击步枪和冲锋枪, 他们的标识是一样的, 只有下面4种

识别时, 截取同位置图片, 做灰度化和全局二值化处理, 将颜色大于 230 的都转为 255, 其他转为 0

按照全自动的 5 颗子弹的位置, 在弹体上取 5 个点, 数纯白色的个数, 即可判断出射击模式

在这里插入图片描述

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

姿态识别 站/蹲/爬

在各种背景下截图, 通过灰度化和自适应二值化操作, 分析可以看出, 姿态的描边上有些位置始终都是黑色的, 不受背景影响

每种姿态找几个这种不受背景影响的点, 作为识别姿态的检测点

识别时, 将截取的姿态图处理好, 按照站/蹲/爬的顺序, 遍历提前记录好的监测点, 如果全是黑色则说明当前处于该种姿态

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

余弹识别

弹夹打光后, 剩余子弹数会变成纯红色, 只需找个点在识别时判断其颜色是否为纯红色即可

在这里插入图片描述
在这里插入图片描述

激活识别 是否持有武器/一号武器/二号武器 (未完成, 做不下去了)

Pubg 只有在确实持枪时, 右下角的武器才会被激活

鼠标滚轮滚动/1/2/3/4/5/G(切雷)/F(落地捡枪)/X(收起武器)/Tab(调整位置) 等可触发激活识别

测试发现, 主界面上右下角武器位和主武器只有下面 4 种情况

  • 无武器, 1 号位和 2 号位上都为空
  • 1 号位上显示 1 号武器
  • 1号位上显示 1 号武器, 2 号位上显示 2 号武器
  • 1号位上显示 2 号武器
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    通过截取武器右边的那个序号, 然后做灰度化和自适应二值化处理, 可以将 1 和 2 变成纯黑色, 然后根据提前记录好的几个点, 来区分上面的几种情况

区分好后再根据位置判断哪把武器被激活

通过观察, 还有下面几个特征

  • 如果是空弹夹且激活, 则武器为纯红色, 不受背景色影响
  • 如果是空弹夹且未激活, 则武器为红色半透明, 受背景色影响严重
  • 如果弹夹非空且激活, 则武器为接近纯白色, 背景色有影响
  • 如果弹夹非空且未激活, 则武技为白色半透明, 受背景色影响严重

然后就做不下去了, 不得已先跳过这个判断

压枪数据

来源于下面参考文章与参考工程, 并未细调数据

FPS游戏自动枪械识别+压枪(以PUBG为例)
GitHub PUBGRecognizeAndGunpress

工程源码

目前只适配了 3440×1440 分辨率

判断当前激活的武器, 可以通过按 1 / 2 来指定激活的武器
我希望能实现自动识别, 而非手动指定的效果. 鉴于技术水平不足的原因, 目前还没做到

除此之外的其他功能都基本 OK 了

相关资源

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

GitHub

python.pubg.weapon.auto.recognize.and.suppress

cfg.py

one = 'one'
two = 'two'
game = 'game'
only = 'only'
semi = 'semi'
auto = 'auto'
name = 'name'
data = 'data'
sight = 'sight'
color = 'color'
point = 'point'
index = 'index'
speed = 'speed'
count = 'count'
armed = 'armed'
empty = 'empty'
stock = 'stock'
stand = 'stand'
squat = 'squat'
prone = 'prone'
weapon = 'weapon'
region = 'region'
points = 'points'
muzzle = 'muzzle'
switch = 'switch'
bullet = 'bullet'
active = 'active'
backpack = 'backpack'
foregrip = 'foregrip'
attitude = 'attitude'
firemode = 'firemode'
interval = 'interval'
ballistic = 'ballistic'

# 检测数据
detect = {
    "3440.1440": {
        backpack: (936, 78, 80, 40),  # 检测背包是否打开的位置
        weapon: {
            region: (2212, 125, 632, 577),  # 一二号武器全截图
            one: {
                index: 1,
                point: (16, 20),  # (y, x), 判断一号武器是否存在的点(纯白色)
                name: (42, 0, 260, 42),
                sight: (365, 29, 62, 31),
                muzzle: (2, 207, 62, 62),
                foregrip: (138, 207, 62, 62),
                stock: (568, 207, 62, 62),
            },
            two: {
                index: 2,
                point: (319, 18),  # (y, x), 判断二号武器是否存在的点(纯白色)
                name: (42, 308, 260, 42),
                sight: (365, 335, 62, 31),
                muzzle: (2, 514, 62, 62),
                foregrip: (138, 514, 62, 62),
                stock: (568, 514, 62, 62),
            },
            name: {
                769: 'ACE32',
                561: 'AKM',
                511: 'AUG',
                1269: 'Beryl M762',
                716: 'G36C',
                568: 'Groza',
                309: 'K2',
                794: 'M16A4',
                646: 'M416',
                1360: 'Mk47 Mutant',
                552: 'QBZ',
                798: 'SCAR-L',
                669: 'Mini14',
                602: 'Mk12',
                588: 'Mk14',
                597: 'QBU',
                494: 'SKS',
                1627: 'SLR',
                464: 'VSS',
                636: 'Crossbow',
                715: 'DP-28',
                710: 'M249 ',
                605: 'MG3',
                846: 'Mortar',
                1325: 'Panzerfaust',
                564: 'DBS',
                423: 'O12',
                556: 'S12K',
                758: 'S1897',
                737: 'S686',
                647: 'AWM',
                872: 'Kar98k',
                993: 'Lynx AMR',
                513: 'M24',
                1611: 'Mosin Nagant',
                740: 'Win94',
                934: 'Micro UZI',
                742: 'MP5K',
                608: 'MP9',
                559: 'P90',
                1207: 'PP-19 Bizon',
                1567: 'Tommy Gun',
                880: 'UMP45',
                624: 'Vector',
                1917: 'Blue Chip Detector',
                1553: 'Drone Tablet',
                1664: 'EMT Gear',
                843: 'Spotter Scope',
                947: 'Tactical Pack',
            },
        },
        attitude: {  # 姿态识别
            region: (1374, 1312, 66, 59),
            stand: [(37, 33), (37, 28), (17, 28), (20, 17)],  # (y, x), 纯黑色
            squat: [(19, 39), (20, 51), (36, 13), (41, 28)],
            prone: [(33, 48), (34, 60), (39, 25), (41, 18)],
        },
        firemode: {  # 武器模式识别
            region: (1649, 1331, 27, 31),
            points: [(3, 13), (8, 13), (14, 13), (19, 13)]
        },
        bullet: (1712, 1324),  # 纯红色则没有子弹
        active: {  # 识别当前使用的武器序号
            region: (2810, 1250, 240, 153),
            one: {  # 主界面右下角一号武器展示位
                # region: (0, 89, 207, 65),
                region: (0, 112, 207, 1),
                1: [(98, 217), (98, 215), (100, 217), (102, 217), (104, 217), (106, 217)],  # 一号武器展示位展示1号武器
                2: [(99, 215), (97, 215), (97, 217), (97, 219), (97, 217), (102, 217), (106, 215), (106, 220)],  # 一号武器展示位展示2号武器
            },
            two: {
                # region: (0, 10, 207, 65),
                region: (0, 33, 207, 1),
                2: [(20, 215), (18, 215), (18, 217), (18, 219), (20, 219), (22, 219), (27, 215), (27, 220)],  # 主界面右下角二号武器展示位
            },
        },
    },
    "2560.1440": {

    },
    "2560.1080": {

    },
    "1920.1080": {

    }
}

# 翻译数据
translation = {
    'ACE32': 'ACE32',
    'AKM': 'AKM',
    'AUG': 'AUG',
    'Beryl M762': 'Beryl M762',
    'G36C': 'G36C',
    'Groza': 'Groza',
    'K2': 'K2',
    'M16A4': 'M16A4',
    'M416': 'M416',
    'Mk47 Mutant': 'Mk47 Mutant',
    'QBZ': 'QBZ',
    'SCAR-L': 'SCAR-L',
    'Mini14': 'Mini14',
    'Mk12': 'Mk12',
    'Mk14': 'Mk14',
    'QBU': 'QBU',
    'SKS': 'SKS',
    'SLR': '自动装填步枪',
    'VSS': 'VSS',
    'Crossbow': '十字弩',
    'DP-28': 'DP-28',
    'M249 ': 'M249 ',
    'MG3': 'MG3',
    'Mortar': '迫击炮',
    'Panzerfaust': '铁拳火箭筒',
    'DBS': 'DBS',
    'O12': 'O12',
    'S12K': 'S12K',
    'S1897': 'S1897',
    'S686': 'S686',
    'AWM': 'AWM',
    'Kar98k': 'Kar98k',
    'Lynx AMR': 'Lynx AMR',
    'M24': 'M24',
    'Mosin Nagant': '莫辛纳甘步枪',
    'Win94': 'Win94',
    'Micro UZI': '微型 UZI',
    'MP5K': 'MP5K',
    'MP9': 'MP9',
    'P90': 'P90',
    'PP-19 Bizon': 'PP-19 Bizon',
    'Tommy Gun': '汤姆逊冲锋枪',
    'UMP45': 'UMP45',
    'Vector': 'Vector',
    'Blue Chip Detector': '蓝色晶片探测器',
    'Drone Tablet': '无人机控制器',
    'EMT Gear': '应急处理装备',
    'Spotter Scope': '观测镜',
    'Tactical Pack': '战术背包',
    'Angled Foregrip': '直角前握把',
    'Haalfgrip': '半截式握把',
    'Laser Sight': '激光瞄准器',
    'Lightweight Grip': '轻型握把',
    'Quiver': '箭袋',
    'Thumbgrip': '拇指握把',
    'Vertical Foregrip': '垂直握把',
    'Choke SG': '扼流圈',
    'Compensator AR': '后座补偿器',
    'Compensator SMG': '枪口补偿器',
    'Compensator SR': '后座补偿器',
    'Duckbill SG': '鸭嘴枪口',
    'Flash Hider AR': '消焰器',
    'Flash Hider SMG': '消焰器',
    'Flash Hider SR': '消焰器',
    'Suppressor AR': '消音器',
    'Suppressor SMG': '消音器',
    'Suppressor SR': '消音器',
    '15x Scope': '15x镜',
    '2x Scope': '2x镜',
    '3x Scope': '3x镜',
    '4x Scope': '4x镜',
    '6x Scope': '6x镜',
    '8x Scope': '8x镜',
    'Holographic Sight': '全息',
    'Red Dot Sight': '红点',
    'Bullet Loops': '子弹袋',
    'Cheek Pad': '托腮板',
    'Folding Stock': '折叠式枪托',
    'Heavy Stock': '重型枪托',
    'Tactical Stock': '战术枪托',
}

# 武器数据, 在列表中的武器才会执行压制
weapons = {
    'M416': {
        interval: 85,  # 全自动射击间隔
        attitude: {  # 姿态影响因子
            stand: 1,
            squat: 0.75,
            prone: 0.5,
        },
        sight: {  # 瞄具影响因子
            '2x Scope': 1.9,
            '3x Scope': 3,
            '4x Scope': 4,
            '6x Scope': 6,
            'Holographic Sight': 1,
            'Red Dot Sight': 1,
        },
        muzzle: {  # 枪口影响因子
            'Compensator AR': 0.78,
            'Flash Hider AR': 0.87,
            'Suppressor AR': 1,
        },
        foregrip: {  # 握把影响因子
            'Angled Foregrip': 1,
            'Haalfgrip': 0.8,
            'Laser Sight': 1,
            'Lightweight Grip': 0.92,
            'Thumbgrip': 0.92,
            'Vertical Foregrip': 0.77,
        },
        stock: {  # 枪托影响因子
            'Heavy Stock': 0.9,
            'Tactical Stock': 0.965,
        },
        ballistic: [36, 23, 24, 23, 33, 34, 34, 34, 40, 40, 40, 40, 41, 41, 41, 42, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 50, 51, 50, 51, 50, 50, 50]
    },
    "ACE32": {
        "ballistic": [
            30,
            30,
            30,
            30,
            40,
            40,
            40,
            40,
            46,
            46,
            46,
            46,
            49,
            49,
            49,
            49,
            56,
            56,
            56,
            56,
            58,
            58,
            58,
            58,
            58,
            58,
            58,
            58,
            58,
            58,
            58,
            58,
            58,
            58,
            58,
            58,
            58,
            58,
            58,
            58,
            58,
            58
        ],
        "interval": 88,
        "attitude": {
            "stand": 1,
            "prone": 0.5,
            "squat": 0.75
        },
        "stock": {
            "Tactical Stock": 0.97
        },
        "foregrip": {
            "Haalfgrip": 0.8,
            "Lightweight Grip": 0.92,
            "Thumbgrip": 0.92,
            "Angled Foregrip": 1,
            "Vertical Foregrip": 0.77
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.8,
            "3x Scope": 2.8,
            "4x Scope": 4,
            "6x Scope": 6
        },
        "muzzle": {
            "Compensator AR": 0.84,
            "Flash Hider AR": 0.84,
            "Suppressor AR": 1
        }
    },
    "AKM": {
        "ballistic": [
            20,
            30,
            30,
            30,
            42,
            42,
            43,
            43,
            46,
            46,
            46,
            47,
            52,
            52,
            53,
            53,
            52,
            53,
            52,
            53,
            52,
            53,
            52,
            53,
            52,
            53,
            52,
            53,
            53,
            54,
            54,
            54,
            53,
            54,
            54,
            54,
            54,
            54,
            54,
            54,
            54,
            54
        ],
        "interval": 100,
        "attitude": {
            "stand": 1,
            "prone": 0.43,
            "squat": 0.75
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.7,
            "3x Scope": 2.6,
            "4x Scope": 3.6,
            "6x Scope": 5.2
        },
        "muzzle": {
            "Compensator AR": 0.84,
            "Flash Hider AR": 0.84,
            "Suppressor AR": 1
        }
    },
    "AUG": {
        "ballistic": [
            15,
            20,
            20,
            20,
            28,
            28,
            28,
            28,
            38,
            28,
            28,
            28,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34,
            34
        ],
        "interval": 86,
        "attitude": {
            "stand": 1,
            "prone": 0.55,
            "squat": 0.8
        },
        "foregrip": {
            "Haalfgrip": 0.82,
            "Lightweight Grip": 0.8,
            "Thumbgrip": 0.92,
            "Angled Foregrip": 1,
            "Vertical Foregrip": 0.8
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.7,
            "3x Scope": 2.6,
            "4x Scope": 3.6,
            "6x Scope": 5.1
        },
        "muzzle": {
            "Compensator AR": 0.86,
            "Flash Hider AR": 0.86,
            "Suppressor AR": 1
        }
    },
    "DP-28": {
        "ballistic": [
            14,
            24,
            24,
            24,
            37,
            37,
            37,
            37,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50
        ],
        "interval": 109,
        "attitude": {
            "stand": 1,
            "prone": 0.04,
            "squat": 0.36
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.7,
            "3x Scope": 2.6,
            "4x Scope": 3.6,
            "6x Scope": 5.1
        }
    },
    "G36C": {
        "ballistic": [
            12,
            22,
            22,
            22,
            32,
            32,
            32,
            32,
            38,
            38,
            38,
            38,
            43,
            43,
            43,
            43,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48,
            48
        ],
        "interval": 86,
        "attitude": {
            "stand": 1,
            "prone": 0.5,
            "squat": 0.75
        },
        "foregrip": {
            "Haalfgrip": 0.75,
            "Lightweight Grip": 0.77,
            "Thumbgrip": 0.92,
            "Angled Foregrip": 1,
            "Vertical Foregrip": 0.77
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.72,
            "3x Scope": 2.6,
            "4x Scope": 3.6,
            "6x Scope": 5.1
        },
        "muzzle": {
            "Compensator AR": 0.84,
            "Flash Hider AR": 0.84,
            "Suppressor AR": 1
        }
    },
    "Groza": {
        "ballistic": [
            15,
            24,
            24,
            24,
            30,
            30,
            30,
            30,
            30,
            30,
            30,
            30,
            38,
            38,
            38,
            38,
            38,
            38,
            38,
            38,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40,
            40
        ],
        "interval": 80,
        "attitude": {
            "stand": 1,
            "prone": 0.45,
            "squat": 0.67
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.75,
            "3x Scope": 2.6,
            "4x Scope": 3.6,
            "6x Scope": 5.2
        },
        "muzzle": {
            "Compensator AR": 1,
            "Flash Hider AR": 1,
            "Suppressor AR": 1
        }
    },
    "K2": {
        "ballistic": [
            15,
            25,
            26,
            26,
            32,
            34,
            34,
            34,
            40,
            40,
            40,
            40,
            40,
            40,
            42,
            42,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            57,
            58,
            58,
            58,
            58,
            58
        ],
        "interval": 43,
        "attitude": {
            "stand": 1,
            "prone": 0.5,
            "squat": 0.75
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.75,
            "3x Scope": 2.6,
            "4x Scope": 3.6,
            "6x Scope": 5.2
        },
        "muzzle": {
            "Compensator AR": 0.84,
            "Flash Hider AR": 0.84,
            "Suppressor AR": 1
        }
    },
    "M16A4": {
        "ballistic": [
            23,
            19,
            19,
            19,
            28,
            29,
            29,
            29,
            26,
            26,
            26,
            27,
            26,
            26,
            26,
            27,
            26,
            26,
            26,
            27,
            26,
            26,
            26,
            27,
            26,
            26,
            26,
            27,
            26,
            26,
            26,
            27,
            26,
            26,
            26,
            27,
            32,
            33,
            33,
            33
        ],
        "interval": 125,
        "attitude": {
            "stand": 1,
            "prone": 0.6,
            "squat": 0.84
        },
        "stock": {
            "Tactical Stock": 1
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.8,
            "3x Scope": 2.75,
            "4x Scope": 3.75,
            "6x Scope": 5.4
        },
        "muzzle": {
            "Compensator AR": 0.93,
            "Flash Hider AR": 0.93,
            "Suppressor AR": 1
        }
    },
    "M249": {
        "ballistic": [
            10,
            18,
            18,
            18,
            18,
            28,
            28,
            28,
            28,
            28,
            20,
            20,
            20,
            20,
            20,
            14,
            14,
            14,
            14,
            14,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16,
            18,
            18,
            16,
            16,
            16
        ],
        "interval": 93.75,
        "attitude": {
            "stand": 1,
            "prone": 0.25,
            "squat": 0.6
        },
        "stock": {
            "Tactical Stock": 1
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.7,
            "3x Scope": 2.6,
            "4x Scope": 3.6,
            "6x Scope": 5.1
        }
    },
    "M4162": {
        "ballistic": [
            20,
            26,
            32,
            34,
            34,
            34,
            40,
            40,
            40,
            40,
            40,
            40,
            42,
            42,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46,
            46
        ],
        "interval": 85,
        "attitude": {
            "stand": 1,
            "prone": 0.5,
            "squat": 0.75
        },
        "stock": {
            "Tactical Stock": 0.965
        },
        "foregrip": {
            "Haalfgrip": 0.77,
            "Lightweight Grip": 0.77,
            "Thumbgrip": 0.92,
            "Angled Foregrip": 1,
            "Vertical Foregrip": 0.77
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.7,
            "3x Scope": 2.6,
            "4x Scope": 3.6,
            "6x Scope": 5.1
        },
        "muzzle": {
            "Compensator AR": 0.84,
            "Flash Hider AR": 0.84,
            "Suppressor AR": 1
        }
    },
    "Beryl M762": {
        "ballistic": [
            28,
            38,
            38,
            38,
            42,
            42,
            43,
            43,
            54,
            54,
            55,
            55,
            54,
            54,
            55,
            55,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62,
            62
        ],
        "interval": 86,
        "attitude": {
            "stand": 1,
            "prone": 0.58,
            "squat": 0.83
        },
        "foregrip": {
            "Haalfgrip": 0.8,
            "Lightweight Grip": 0.78,
            "Thumbgrip": 0.93,
            "Angled Foregrip": 1,
            "Vertical Foregrip": 0.78
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.72,
            "3x Scope": 2.62,
            "4x Scope": 3.62,
            "6x Scope": 5.2
        },
        "muzzle": {
            "Compensator AR": 0.86,
            "Flash Hider AR": 0.86,
            "Suppressor AR": 1
        }
    },
    "MG3": {
        "ballistic": [
            22,
            16,
            16,
            16,
            17,
            18,
            17,
            18,
            11,
            12,
            12,
            12,
            11,
            12,
            12,
            12,
            11,
            12,
            12,
            12,
            11,
            12,
            12,
            12,
            11,
            12,
            12,
            12,
            11,
            12,
            12,
            12,
            11,
            12,
            12,
            12,
            11,
            12,
            12,
            12,
            11,
            12,
            12,
            12,
            11,
            12,
            12,
            12,
            11,
            12,
            12,
            12,
            9,
            9,
            9,
            9
        ],
        "interval": 76.25,
        "attitude": {
            "stand": 1,
            "prone": 0.25,
            "squat": 0.45
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.7,
            "3x Scope": 2.6,
            "4x Scope": 3.6,
            "6x Scope": 5.1
        }
    },
    "Mini14": {
        "ballistic": [
            14,
            24,
            24,
            24,
            37,
            37,
            37,
            37,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50
        ],
        "interval": 125,
        "attitude": {
            "stand": 1,
            "prone": 0.58,
            "squat": 0.73
        },
        "sight": {
            "none": 1,
            "15x Scope": 10.6,
            "2x Scope": 1.8,
            "3x Scope": 2.65,
            "4x Scope": 3.65,
            "6x Scope": 5.3,
            "8x Scope": 7.1
        },
        "muzzle": {
            "Compensator AR": 0.9,
            "Flash Hider AR": 0.9,
            "Suppressor AR": 1,
            "Compensator SR": 0.9,
            "Flash Hider SR": 0.9,
            "Suppressor SR": 1
        }
    },
    "Mk12": {
        "ballistic": [
            24,
            24,
            24,
            24,
            37,
            37,
            37,
            37,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50
        ],
        "interval": 93.8,
        "attitude": {
            "stand": 1,
            "prone": 0.2,
            "squat": 0.74
        },
        "foregrip": {
            "Haalfgrip": 0.92,
            "Lightweight Grip": 0.77,
            "Thumbgrip": 0.96,
            "Angled Foregrip": 1,
            "Vertical Foregrip": 0.85
        },
        "sight": {
            "none": 1,
            "15x Scope": 10.85,
            "2x Scope": 1.8,
            "3x Scope": 2.75,
            "4x Scope": 3.85,
            "6x Scope": 5.5,
            "8x Scope": 7.35
        },
        "muzzle": {
            "Compensator AR": 0.9,
            "Flash Hider AR": 0.9,
            "Suppressor AR": 1,
            "Compensator SR": 0.9,
            "Flash Hider SR": 0.9,
            "Suppressor SR": 1
        }
    },
    "Mk14": {
        "ballistic": [
            11,
            11,
            11,
            12,
            4,
            4,
            4,
            5,
            12,
            13,
            12,
            13,
            14,
            14,
            14,
            15,
            38,
            38,
            38,
            39,
            40,
            41,
            41,
            41,
            40,
            41,
            41,
            41,
            40,
            41,
            41,
            41,
            40,
            41,
            41,
            41,
            40,
            41,
            41,
            41,
            40,
            41,
            41,
            41,
            60,
            61,
            61,
            61
        ],
        "interval": 22.5,
        "attitude": {
            "stand": 1,
            "prone": 0.17,
            "squat": 0.68
        },
        "stock": {
            "Cheek Pad": 0.73
        },
        "sight": {
            "none": 1,
            "15x Scope": 10.2,
            "2x Scope": 1.7,
            "3x Scope": 2.55,
            "4x Scope": 3.55,
            "6x Scope": 5.2,
            "8x Scope": 6.9
        },
        "muzzle": {
            "Compensator AR": 0.89,
            "Flash Hider AR": 0.89,
            "Suppressor AR": 1,
            "Compensator SR": 0.89,
            "Flash Hider SR": 0.89,
            "Suppressor SR": 1
        }
    },
    "Mk47 Mutant": {
        "ballistic": [
            14,
            24,
            24,
            24,
            37,
            37,
            37,
            37,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50
        ],
        "interval": 125,
        "attitude": {
            "stand": 1,
            "prone": 0.68,
            "squat": 0.85
        },
        "stock": {
            "Tactical Stock": 0.98
        },
        "foregrip": {
            "Haalfgrip": 0.86,
            "Lightweight Grip": 0.68,
            "Thumbgrip": 0.92,
            "Angled Foregrip": 1,
            "Vertical Foregrip": 0.82
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.75,
            "3x Scope": 2.6,
            "4x Scope": 3.65,
            "6x Scope": 5.2
        },
        "muzzle": {
            "Compensator AR": 0.87,
            "Flash Hider AR": 0.87,
            "Suppressor AR": 1
        }
    },
    "MP5K": {
        "ballistic": [
            17,
            17,
            17,
            17,
            17,
            33,
            33,
            33,
            33,
            33,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29,
            29
        ],
        "interval": 83.75,
        "attitude": {
            "stand": 1,
            "prone": 0.5,
            "squat": 0.65
        },
        "stock": {
            "Tactical Stock": 1
        },
        "foregrip": {
            "Haalfgrip": 0.89,
            "Lightweight Grip": 0.74,
            "Thumbgrip": 0.91,
            "Angled Foregrip": 1,
            "Vertical Foregrip": 0.74
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.9,
            "3x Scope": 2.9,
            "4x Scope": 4,
            "6x Scope": 5.8
        },
        "muzzle": {
            "Suppressor SMG": 1,
            "Flash Hider SMG": 1,
            "Compensator SMG": 1
        }
    },
    "P90": {
        "ballistic": [
            7,
            14,
            14,
            14,
            14,
            14,
            14,
            19,
            19,
            19,
            19,
            19,
            19,
            19,
            15,
            15,
            15,
            15,
            15,
            15,
            15,
            12,
            12,
            12,
            12,
            12,
            12,
            12,
            14,
            14,
            14,
            14,
            14,
            14,
            14,
            14,
            13,
            13,
            13,
            13,
            13,
            13,
            13,
            13,
            13,
            12,
            12,
            12,
            12,
            12
        ],
        "interval": 105,
        "attitude": {
            "stand": 1,
            "prone": 0.65,
            "squat": 0.75
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.8
        }
    },
    "PP-19 Bizon": {
        "ballistic": [
            8,
            16,
            16,
            16,
            16,
            25,
            25,
            25,
            25,
            26,
            23,
            23,
            23,
            23,
            23,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21,
            21
        ],
        "interval": 107.5,
        "attitude": {
            "stand": 1,
            "prone": 0.68,
            "squat": 0.78
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.9,
            "3x Scope": 2.9,
            "4x Scope": 4,
            "6x Scope": 5.7
        },
        "muzzle": {
            "Suppressor SMG": 1,
            "Flash Hider SMG": 1,
            "Compensator SMG": 1
        }
    },
    "QBU": {
        "ballistic": [
            11,
            24,
            24,
            24,
            37,
            37,
            37,
            37,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50
        ],
        "interval": 62.5,
        "attitude": {
            "stand": 1,
            "prone": 0.2,
            "squat": 0.74
        },
        "sight": {
            "none": 1,
            "15x Scope": 10.6,
            "2x Scope": 1.8,
            "3x Scope": 2.65,
            "4x Scope": 3.75,
            "6x Scope": 5.3,
            "8x Scope": 7.1
        },
        "muzzle": {
            "Compensator AR": 0.88,
            "Flash Hider AR": 0.88,
            "Suppressor AR": 1,
            "Compensator SR": 0.88,
            "Flash Hider SR": 0.88,
            "Suppressor SR": 1
        }
    },
    "QBZ": {
        "ballistic": [
            14,
            24,
            24,
            24,
            30,
            30,
            30,
            30,
            39,
            39,
            39,
            39,
            47,
            47,
            47,
            48,
            47,
            47,
            47,
            48,
            47,
            47,
            47,
            48,
            47,
            47,
            47,
            47,
            47,
            48,
            47,
            47,
            47,
            47,
            47,
            47,
            47,
            47,
            47,
            47,
            47,
            48
        ],
        "interval": 92,
        "attitude": {
            "stand": 1,
            "prone": 0.5,
            "squat": 0.75
        },
        "foregrip": {
            "Haalfgrip": 0.75,
            "Lightweight Grip": 0.77,
            "Thumbgrip": 0.92,
            "Angled Foregrip": 1,
            "Vertical Foregrip": 0.77
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.72,
            "3x Scope": 2.6,
            "4x Scope": 3.6,
            "6x Scope": 5.1
        },
        "muzzle": {
            "Compensator AR": 0.84,
            "Flash Hider AR": 0.84,
            "Suppressor AR": 1
        }
    },
    "SCAR-L": {
        "ballistic": [
            15,
            23,
            23,
            24,
            30,
            30,
            30,
            30,
            38,
            38,
            38,
            39,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43,
            43
        ],
        "interval": 96,
        "attitude": {
            "stand": 1,
            "prone": 0.53,
            "squat": 0.75
        },
        "foregrip": {
            "Haalfgrip": 0.77,
            "Lightweight Grip": 0.77,
            "Thumbgrip": 0.92,
            "Angled Foregrip": 1,
            "Vertical Foregrip": 0.77
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.72,
            "3x Scope": 2.6,
            "4x Scope": 3.6,
            "6x Scope": 5.1
        },
        "muzzle": {
            "Compensator AR": 0.84,
            "Flash Hider AR": 0.84,
            "Suppressor AR": 1
        }
    },
    "SKS": {
        "ballistic": [
            14,
            24,
            24,
            24,
            37,
            37,
            37,
            37,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50
        ],
        "interval": 125,
        "attitude": {
            "stand": 1,
            "prone": 0.44,
            "squat": 0.64
        },
        "stock": {
            "Cheek Pad": 0.84
        },
        "foregrip": {
            "Haalfgrip": 0.88,
            "Lightweight Grip": 0.65,
            "Thumbgrip": 0.94,
            "Angled Foregrip": 1,
            "Vertical Foregrip": 0.78
        },
        "sight": {
            "none": 1,
            "15x Scope": 10.3,
            "2x Scope": 1.7,
            "3x Scope": 2.55,
            "4x Scope": 3.55,
            "6x Scope": 5.1,
            "8x Scope": 6.8
        },
        "muzzle": {
            "Compensator AR": 0.88,
            "Flash Hider AR": 0.88,
            "Suppressor AR": 1,
            "Compensator SR": 0.88,
            "Flash Hider SR": 0.88,
            "Suppressor SR": 1
        }
    },
    "SLR": {
        "ballistic": [
            14,
            24,
            24,
            24,
            37,
            37,
            37,
            37,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50,
            50
        ],
        "interval": 125,
        "attitude": {
            "stand": 1,
            "prone": 0.43,
            "squat": 0.64
        },
        "stock": {
            "Cheek Pad": 0.7
        },
        "sight": {
            "none": 1,
            "15x Scope": 10.3,
            "2x Scope": 1.7,
            "3x Scope": 2.55,
            "4x Scope": 3.55,
            "6x Scope": 5.1,
            "8x Scope": 6.8
        },
        "muzzle": {
            "Compensator AR": 0.84,
            "Flash Hider AR": 0.84,
            "Suppressor AR": 1,
            "Compensator SR": 0.84,
            "Flash Hider SR": 0.84,
            "Suppressor SR": 1
        }
    },
    "Tommy Gun": {
        "ballistic": [
            10,
            18,
            18,
            18,
            18,
            26,
            26,
            26,
            26,
            39,
            39,
            39,
            39,
            39,
            39,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41,
            41
        ],
        "interval": 90,
        "attitude": {
            "stand": 1,
            "prone": 0.58,
            "squat": 0.68
        },
        "foregrip": {
            "Vertical Foregrip": 0.77
        },
        "sight": {
            "none": 1
        }
    },
    "UMP45": {
        "ballistic": [
            10,
            19,
            19,
            19,
            26,
            26,
            26,
            28,
            26,
            26,
            26,
            28,
            32,
            32,
            32,
            34,
            30,
            30,
            30,
            32,
            30,
            30,
            30,
            32,
            30,
            30,
            30,
            32,
            30,
            30,
            30,
            30,
            30,
            30,
            30,
            30,
            30
        ],
        "interval": 90,
        "attitude": {
            "stand": 1,
            "prone": 0.64,
            "squat": 0.74
        },
        "foregrip": {
            "Haalfgrip": 0.84,
            "Lightweight Grip": 0.84,
            "Thumbgrip": 0.91,
            "Angled Foregrip": 1,
            "Vertical Foregrip": 0.77
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.9,
            "3x Scope": 2.9,
            "4x Scope": 4,
            "6x Scope": 5.7
        },
        "muzzle": {
            "Suppressor SMG": 1,
            "Flash Hider SMG": 1,
            "Compensator SMG": 1
        }
    },
    "Micro UZI": {
        "ballistic": [
            8,
            8,
            8,
            8,
            8,
            16,
            16,
            16,
            16,
            16,
            24,
            24,
            24,
            24,
            24,
            31,
            31,
            31,
            31,
            31,
            31,
            31,
            31,
            31,
            31,
            31,
            31,
            31,
            31,
            31,
            31,
            32,
            32,
            32,
            32,
            32,
            32
        ],
        "interval": 60,
        "attitude": {
            "stand": 1,
            "prone": 0.6,
            "squat": 0.75
        },
        "stock": {
            "Folding Stock": 0.7
        },
        "sight": {
            "none": 1
        },
        "muzzle": {
            "Suppressor SMG": 1,
            "Flash Hider SMG": 0.85,
            "Compensator SMG": 0.65
        }
    },
    "Vector": {
        "ballistic": [
            7,
            13,
            13,
            13,
            14,
            24,
            24,
            24,
            24,
            25,
            25,
            25,
            25,
            25,
            25,
            37,
            37,
            37,
            37,
            37,
            33,
            33,
            33,
            33,
            33,
            33,
            33,
            33,
            33,
            33,
            33,
            33,
            33,
            33,
            33
        ],
        "interval": 68.75,
        "attitude": {
            "stand": 1,
            "prone": 0.64,
            "squat": 0.74
        },
        "stock": {
            "Tactical Stock": 0.97
        },
        "foregrip": {
            "Haalfgrip": 0.85,
            "Lightweight Grip": 0.85,
            "Thumbgrip": 0.92,
            "Angled Foregrip": 1,
            "Vertical Foregrip": 0.8
        },
        "sight": {
            "none": 1,
            "2x Scope": 1.9,
            "3x Scope": 2.9,
            "4x Scope": 4,
            "6x Scope": 5.7
        },
        "muzzle": {
            "Suppressor SMG": 1,
            "Flash Hider SMG": 1,
            "Compensator SMG": 1
        }
    },
    "VSS": {
        "ballistic": [
            29,
            29,
            29,
            29,
            86,
            86,
            86,
            86,
            86,
            86,
            86,
            86,
            92,
            92,
            94,
            94,
            106,
            106,
            106,
            106,
            106,
            106
        ],
        "interval": 86,
        "attitude": {
            "stand": 1,
            "prone": 0.57,
            "squat": 0.75
        },
        "stock": {
            "Cheek Pad": 0.77
        },
        "sight": {
            "none": 1
        }
    }

}

structure.py

import cfg


class Weapon:

    def __init__(self, name, sight, muzzle, foregrip, stock):
        self.name = name
        self.sight = sight
        self.muzzle = muzzle
        self.foregrip = foregrip
        self.stock = stock
        self.data = cfg.weapons.get(self.name)
        self.suppress = True if self.data else False  # 该武器是否可以执行压制
        if self.data:
            self.interval = self.data.get(cfg.interval)  # 射击间隔
            self.ballistic = self.data.get(cfg.ballistic)  # 垂直弹道
            self.factor = 1
            attachment = self.data.get(cfg.sight)
            if attachment:
                self.factor *= attachment.get(self.sight, 1)
            attachment = self.data.get(cfg.muzzle)
            if attachment:
                self.factor *= attachment.get(self.muzzle, 1)
            attachment = self.data.get(cfg.foregrip)
            if attachment:
                self.factor *= attachment.get(self.foregrip, 1)
            attachment = self.data.get(cfg.stock)
            if attachment:
                self.factor *= attachment.get(self.stock, 1)

    def attitude(self, attitude):
        """
        根据传入的姿态, 获取该武器对应数据中的姿态影响因子
        """
        return self.data.get(cfg.attitude).get(attitude, 1)

    def __str__(self):
        name = cfg.translation.get(self.name)
        sight = cfg.translation.get(self.sight)
        muzzle = cfg.translation.get(self.muzzle)
        foregrip = cfg.translation.get(self.foregrip)
        stock = cfg.translation.get(self.stock)
        string = f'[{name}]'
        if sight:
            string += f', {sight}'
        if muzzle:
            string += f', {muzzle}'
        if foregrip:
            string += f', {foregrip}'
        if stock:
            string += f', {stock}'
        # print(f'武器:{self.name}, 瞄具:{self.sight}, 枪口:{self.muzzle}, 握把:{self.foregrip}, 枪托:{self.stock}')
        return string

toolkit.py

import os
import time

import cv2
import d3dshot
import mss as pymss
import numpy as np
from skimage import measure  # pip install scikit-image

from win32api import GetSystemMetrics  # conda install pywin32
from win32con import SRCCOPY, SM_CXSCREEN, SM_CYSCREEN
from win32gui import GetDesktopWindow, GetWindowDC, DeleteObject, GetWindowText, GetForegroundWindow, GetDC, ReleaseDC, GetPixel
from win32ui import CreateDCFromHandle, CreateBitmap


class Capturer:

    @staticmethod
    def win(region):
        """
        region: tuple, (left, top, width, height)
        conda install pywin32, 用 pip 装的一直无法导入 win32ui 模块, 找遍各种办法都没用, 用 conda 装的一次成功
        """
        left, top, width, height = region
        hWin = GetDesktopWindow()
        hWinDC = GetWindowDC(hWin)
        srcDC = CreateDCFromHandle(hWinDC)
        memDC = srcDC.CreateCompatibleDC()
        bmp = CreateBitmap()
        bmp.CreateCompatibleBitmap(srcDC, width, height)
        memDC.SelectObject(bmp)
        memDC.BitBlt((0, 0), (width, height), srcDC, (left, top), SRCCOPY)
        array = bmp.GetBitmapBits(True)
        DeleteObject(bmp.GetHandle())
        memDC.DeleteDC()
        srcDC.DeleteDC()
        ReleaseDC(hWin, hWinDC)
        img = np.frombuffer(array, dtype='uint8')
        img.shape = (height, width, 4)
        return img

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

    @staticmethod
    def d3d(instance, region=None):
        """
        DXGI 普通模式
        region: tuple, (left, top, width, height)
        因为 D3DShot 在 Python 3.9 里会和 pillow 版本冲突, 所以使用大佬修复过的版本来替代
        pip install git+https://github.com/fauskanger/D3DShot#egg=D3DShot
        """
        if region:
            left, top, width, height = region
            return instance.screenshot((left, top, left + width, top + height))
        else:
            return instance.screenshot()

    @staticmethod
    def d3d_latest_frame(instance):
        """
        DXGI 缓存帧模式
        """
        return instance.get_latest_frame()

    @staticmethod
    def instance(mss=False, d3d=False, buffer=False, frame_buffer_size=60, target_fps=60, region=None):
        if mss:
            return pymss.mss()
        elif d3d:
            """
            buffer: 是否使用缓存帧模式
                否: 适用于 dxgi.screenshot
                是: 适用于 dxgi.get_latest_frame, 需传入 frame_buffer_size, target_fps, region
            """
            if not buffer:
                return d3dshot.create(capture_output="numpy")
            else:
                dxgi = d3dshot.create(capture_output="numpy", frame_buffer_size=frame_buffer_size)
                left, top, width, height = region
                dxgi.capture(target_fps=target_fps, region=(left, top, left + width, top + height))  # region: left, top, right, bottom, 需要适配入参为 left, top, width, height 格式的 region
                return dxgi

    @staticmethod
    def grab(win=False, mss=False, d3d=False, instance=None, region=None, buffer=False, convert=False):
        """
        win:
            region: tuple, (left, top, width, height)
        mss:
            instance: mss instance
            region: tuple, (left, top, width, height)
        d3d:
            buffer: 是否为缓存帧模式
                否: 需要 region
                是: 不需要 region
            instance: d3d instance, 区分是否为缓存帧模式
            region: tuple, (left, top, width, height), 区分是否为缓存帧模式
        convert: 是否转换为 opencv 需要的 numpy BGR 格式, 转换结果可直接用于 opencv
        """
        # 补全范围
        if (win or mss or (d3d and not buffer)) and not region:
            w, h = Monitor.resolution()
            region = 0, 0, w, h
        # 范围截图
        if win:
            img = Capturer.win(region)
        elif mss:
            img = Capturer.mss(instance, region)
        elif d3d:
            if not buffer:
                img = Capturer.d3d(instance, region)
            else:
                img = Capturer.d3d_latest_frame(instance)
        else:
            img = Capturer.win(region)
            win = True
        # 图片转换
        if convert:
            if win:
                img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
            elif mss:
                img = cv2.cvtColor(np.array(img), cv2.COLOR_BGRA2BGR)
            elif d3d:
                img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
        return img


class Monitor:

    @staticmethod
    def resolution():
        """
        显示分辨率
        """
        w = GetSystemMetrics(SM_CXSCREEN)
        h = GetSystemMetrics(SM_CYSCREEN)
        return w, h

    @staticmethod
    def center():
        """
        屏幕中心点
        """
        w, h = Monitor.resolution()
        return w // 2, h // 2


class Timer:

    @staticmethod
    def cost(interval):
        """
        转换耗时, 输入纳秒间距, 转换为合适的单位
        """
        if interval < 1000:
            return f'{interval}ns'
        elif interval < 1_000_000:
            return f'{round(interval / 1000, 3)}us'
        elif interval < 1_000_000_000:
            return f'{round(interval / 1_000_000, 3)}ms'
        else:
            return f'{round(interval / 1_000_000_000, 3)}s'


class Image:

    @staticmethod
    def gray(img, max=False):
        """
        灰度化
        :param img: OpenCV BGR
        :param max: 使用BGR3通道中的最大值作为灰度色值
        """
        """
        BGR3通道色值取平均值就是灰度色值, 灰度图将BGR3通道转换为灰度通道
        """
        if not max:
            return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        else:
            new = np.zeros(img.shape[:2], np.uint8)
            for row in range(0, img.shape[0]):
                for col in range(0, img.shape[1]):
                    (b, g, r) = img[row][col]
                    value = (b if b >= g else g)
                    new[row][col] = (value if value >= r else r)
            return new

    @staticmethod
    def binary(img, adaptive=False, threshold=None, block=3, c=1):
        """
        二值化
        :param img: 灰度图
        :param adaptive: 是否自适应二值化
        :param threshold: 二值化阈值(全局二值化), 大于该值的转为白色, 其他值转为黑色
        :param block: 分割的邻域大小(自适应二值化). 值越大, 参与计算阈值的邻域面积越大, 细节轮廓就变得越少, 整体轮廓将越粗越明显
        :param c: 常数(自适应二值化), 可复数. 值越大, 每个邻域内计算出的阈值将越小, 转换为 maxVal 的点将越多, 整体图像白色像素将越多
        """
        if not adaptive:
            # 全局二值化
            _, img = cv2.threshold(img, threshold, 255, cv2.THRESH_BINARY)
            """
            threshold(src, thresh, maxVal, type, dst=None)
                src: 灰度图
                thresh: 阈值
                maxVal: 指定的最大色值
                type:
                    THRESH_BINARY: 二值化, 大于阈值的赋最大色值, 其他赋0
                    THRESH_BINARY_INV: 二值化反转, 与 THRESH_BINARY 相反, 大于阈值的赋0, 其他赋最大色值
                    THRESH_TRUNC: 截断操作, 大于阈值的赋最大色值, 其他不变
                    THRESH_TOZERO: 化零操作, 大于阈值的不变, 其他赋0
                    THRESH_TOZERO_INV: 化零操作反转, 大于阈值的赋0, 其他不变
            """
        else:
            # 自适应二值化
            img = cv2.adaptiveThreshold(img, maxValue=255, adaptiveMethod=cv2.ADAPTIVE_THRESH_GAUSSIAN_C, thresholdType=cv2.THRESH_BINARY, blockSize=block, C=c)
            """
            adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C, dst=None)
                src: 灰度图
                maxValue: 指定的最大色值
                adaptiveMethod: 自适应方法。有2种:ADAPTIVE_THRESH_MEAN_C 或 ADAPTIVE_THRESH_GAUSSIAN_C
                    ADAPTIVE_THRESH_MEAN_C,为局部邻域块的平均值,该算法是先求出块中的均值。
                    ADAPTIVE_THRESH_GAUSSIAN_C,为局部邻域块的高斯加权和。该算法是在区域中(x, y)周围的像素根据高斯函数按照他们离中心点的距离进行加权计算。
                thresholdType: 二值化方法,只能选 THRESH_BINARY 或者 THRESH_BINARY_INV
                    THRESH_BINARY: 二值化, 大于阈值的赋最大色值, 其他赋0
                    THRESH_BINARY_INV: 二值化反转, 与 THRESH_BINARY 相反, 大于阈值的赋0, 其他赋最大色值
                blockSize: 分割计算的区域大小,取奇数
                    当blockSize越大,参与计算阈值的区域也越大,细节轮廓就变得越少,整体轮廓越粗越明显
                C:常数,每个区域计算出的阈值的基础上在减去这个常数作为这个区域的最终阈值,可以为负数
                    当C越大,每个像素点的N*N邻域计算出的阈值就越小,中心点大于这个阈值的可能性也就越大,设置成255的概率就越大,整体图像白色像素就越多,反之亦然。
            """
        return img

    @staticmethod
    def binary_remove_small_objects(img, threshold):
        """
        消除二值图像中面积小于某个阈值的连通域(消除孤立点)
        :param img: 二值图像(白底黑图)
        :param threshold: 符合面积条件大小的阈值
        """
        img_label, num = measure.label(img, background=255, connectivity=2, return_num=True)  # 输出二值图像中所有的连通域
        props = measure.regionprops(img_label)  # 输出连通域的属性,包括面积等
        resMatrix = np.zeros(img_label.shape)  # 创建0图
        for i in range(0, len(props)):
            if props[i].area > threshold:
                tmp = (img_label == i + 1).astype(np.uint8)
                resMatrix += tmp  # 组合所有符合条件的连通域
        resMatrix *= 255
        return 255 - resMatrix  # 本来输出的是黑底百图, 这里特意转换了黑白

    @staticmethod
    def similarity(img1, img2, block=10):
        """
        求两张二值化图片的相似度(简单实现)
        :param img1: 图片1
        :param img2: 图片2
        :param block: 分块对比的块边长, 从1开始, 边长越大精度越低
        """
        if img1.shape != img2.shape:
            return 0
        # 遍历图片, 计算同一位置相同色占总色数的比例
        height, width = img1.shape  # 经过处理后, 通道数只剩1个了
        # 相似度列表
        similarities = []
        # 根据给定的block大小计算分割的行列数, 将图片分为row行col列个格子(注意最后一行和最后一列的格子不一定是block大小)
        row = 1 if block >= height else (height // block + (0 if height % block == 0 else 1))
        col = 1 if block >= width else (width // block + (0 if width % block == 0 else 1))
        # print(f'图片宽度:{width},高度:{height}, 以块边长:{block}, 分为{row}行{col}列')
        for i in range(0, row):
            for j in range(0, col):
                # print('-')
                # 计算当前格子的w和h
                w = block if j + 1 < col else (width - (col - 1) * block)
                h = block if i + 1 < row else (height - (row - 1) * block)
                # print(f'当前遍历第{i + 1}行第{j + 1}列的块, 该块的宽度:{w},高度:{h}, 即该块有{h}行{w}列')
                counter = 0
                for x in range(block * i, block * i + h):
                    for y in range(block * j, block * j + w):
                        # print(f'x:{x},y:{y}')
                        if img1[x][y] == img2[x][y]:
                            counter += 1
                similarity = counter / (w * h)
                # print(f'当前块的相似度是:{similarity}')
                similarities.append(similarity ** 3)
        # print(similarities)
        return sum(similarities) / len(similarities)

    @staticmethod
    def cut(img, region):
        """
        从 img 中截取 region 范围. 入参图片需为 OpenCV 格式
        """
        left, top, width, height = region
        return img[top:top + height, left:left + width]

    @staticmethod
    def convert(img, gray=False, binary=None, remove=None):
        """
        图片(OpenCV BGR 格式)做灰度化和二值化处理
        :param img: OpenCV BGR 图片
        :param gray: 是否做灰度化处理
        :param binary: dict 格式, 非 None 做二值化处理
            具体参照 binary 方法的参数说明
            adaptive: 是否自适应二值化
            threshold: 非自适应二值化, 二值化阈值
            block: 自适应二值化邻域大小
            c: 常数
        :param remove: dict 格式, 非 None 做孤立点消除操作
            threshold: 连通域面积阈值, 小于该面积的连通域将被消除(黑转白)
        """
        if gray:
            img = Image.gray(img)
            if binary:
                if not isinstance(binary, dict):
                    return img
                adaptive = binary.get('adaptive')
                threshold = binary.get('threshold')
                block = binary.get('block')
                c = binary.get('c')
                img = Image.binary(img, adaptive, threshold, block, c)
                if remove:
                    if not isinstance(remove, dict):
                        return img
                    threshold = remove.get('threshold')
                    img = Image.binary_remove_small_objects(img, threshold)
        return img

    @staticmethod
    def read(path, gray=False, binary=None, remove=None):
        """
        读取一张图片(OpenCV BGR 格式)并做灰度化和二值化处理
        :param path: 图片路径
        :param gray: 是否做灰度化处理
        :param binary: dict 格式, 非 None 做二值化处理
        :param remove: dict 格式, 非 None 做孤立点消除操作
        """
        img = cv2.imread(path)
        # img = cv2.imdecode(np.fromfile(path, dtype=np.uint8), cv2.IMREAD_COLOR)  # 适配中文字符路径
        img = Image.convert(img, gray, binary, remove)
        return img

    @staticmethod
    def load(directory, gray=False, binary=None, remove=None):
        """
        递归载入指定路径下的所有图片(OpenCV BGR 格式), 按照 (name, img) 的格式组合成为列表并返回
        """
        imgs = []
        for item in os.listdir(directory):
            path = os.path.join(directory, item)
            if os.path.isdir(path):
                temp = Image.load(path, gray, binary, remove)
                imgs.extend(temp)
            elif os.path.isfile(path):
                name = os.path.splitext(item)[0]
                img = Image.read(path, gray, binary, remove)
                imgs.append((name, img))
        return imgs if imgs else None


import cfg
from structure import Weapon


class Pubg:

    @staticmethod
    def game():
        """
        是否游戏窗体在最前
        """
        return '绝地求生' in GetWindowText(GetForegroundWindow())

    def __init__(self):
        w, h = Monitor.resolution()
        self.key = f'{w}.{h}'  # 分辨率键
        self.binary = {'adaptive': True, 'block': 3, 'c': 1}
        self.remove = {'threshold': 10}
        self.std_img_backpack = Image.read(rf'image/{self.key}/backpack.png', gray=True, binary=self.binary, remove=self.remove)
        self.std_imgs_sight_1 = Image.load(rf'image/{self.key}/weapon/attachment/sight/1', gray=True, binary=self.binary, remove=self.remove)
        self.std_imgs_sight_2 = Image.load(rf'image/{self.key}/weapon/attachment/sight/2', gray=True, binary=self.binary, remove=self.remove)
        self.std_imgs_muzzle = Image.load(rf'image/{self.key}/weapon/attachment/muzzle', gray=True, binary=self.binary, remove=self.remove)
        self.std_imgs_foregrip = Image.load(rf'image/{self.key}/weapon/attachment/foregrip', gray=True, binary=self.binary, remove=self.remove)
        self.std_imgs_stock = Image.load(rf'image/{self.key}/weapon/attachment/stock', gray=True, binary=self.binary, remove=self.remove)
        self.std_names = cfg.detect.get(self.key).get(cfg.weapon).get(cfg.name)

    def backpack(self):
        """
        是否在背包界面
        """
        region = cfg.detect.get(self.key).get(cfg.backpack)
        img = Capturer.grab(win=True, region=region, convert=True)
        img = Image.convert(img, gray=True, binary=self.binary, remove=self.remove)
        return Image.similarity(self.std_img_backpack, img) > 0.9

    def weapon(self):
        """
        在背包库存界面识别两把主武器及其配件
        """
        data = cfg.detect.get(self.key).get(cfg.weapon)
        region = data.get(cfg.region)
        # 截图主武器部分
        img = Capturer.grab(win=True, region=region, convert=True)
        # 识别两把主武器武器
        weapon1 = self.recognize(img, data.get(cfg.one))
        weapon2 = self.recognize(img, data.get(cfg.two))
        return weapon1, weapon2

    def bullet(self):
        """
        是否有子弹
        效率很低且不稳定, 单点检测都要耗时1-10ms
        获取颜色, COLORREF 格式, 0x00FFFFFF
        结果是int,
        可以通过 print(hex(color)) 查看十六进制值
        可以通过 print(color == 0x00FFFFFF) 进行颜色判断
        """
        x, y = cfg.detect.get(self.key).get(cfg.bullet)
        hdc = GetDC(None)
        color = GetPixel(hdc, x, y)
        # print(color)
        ReleaseDC(None, hdc)
        return color != 255

    def attitude(self):
        """
        姿态识别
        """
        data = cfg.detect.get(self.key).get(cfg.attitude)
        region = data.get(cfg.region)
        # 截图姿态部分
        img = Capturer.grab(win=True, region=region, convert=True)
        # 灰度化二值化
        img = Image.convert(img, gray=True, binary=self.binary)
        # cv2.imwrite('1.jpg', img)
        # 判断是否是站立
        counter = 0
        points = data.get(cfg.stand)
        for point in points:
            if img[point] == 0:
                counter += 1
        if counter == len(points):
            return cfg.stand
        # 判断是否是蹲下
        counter = 0
        points = data.get(cfg.squat)
        for point in points:
            if img[point] == 0:
                counter += 1
        if counter == len(points):
            return cfg.squat
        # 判断是否是趴卧
        counter = 0
        points = data.get(cfg.prone)
        for point in points:
            if img[point] == 0:
                counter += 1
        if counter == len(points):
            return cfg.prone
        # 不是3种姿态
        return None

    def firemode(self):
        """
        射击模式识别, 只限突击步枪和冲锋枪
        """
        data = cfg.detect.get(self.key).get(cfg.firemode)
        region = data.get(cfg.region)
        # 截图模式部分
        img = Capturer.grab(win=True, region=region, convert=True)
        # 灰度化
        img = Image.gray(img)
        # 二值化
        img = Image.binary(img, threshold=230)
        # cv2.imwrite(f'{int(time.perf_counter_ns())}.jpg', img)
        # 判断射击模式
        counter = 0
        points = data.get(cfg.points)
        for point in points:
            # print(img[point])
            if img[point] == 255:
                counter += 1
        if counter == 1:
            return cfg.only
        elif counter == 2 or counter == 3:
            return cfg.semi
        elif counter == 4:
            return cfg.auto
        # 非四种射击模式
        return None

    def index(self):
        """
        1/2号武器识别, 0:未持有1/2武器, 1:持有1号武器, 2:持有2号武器
        判定时机:
        鼠标滚轮滚动/1/2/3/4/5/G(切雷)/F(落地捡枪)/X(收起武器)/Tab(调整位置)
        投掷武器,近战武器和单发火箭炮等,用光后不会导致切换武器
        能量和药包等消耗品,使用前如果持有武器,使用后会切回该武器,使用前未持有武器,使用后不会切换武器
        """
        """
        测试发现
        主界面上右下角武器位和主武器只有下面3种情况
        1号位上显示1号武器
        1号位上显示1号武器, 2号位上显示2号武器
        1号位上显示2号武器
        """
        data = cfg.detect.get(self.key).get(cfg.active)
        region = data.get(cfg.region)
        # 截图模式部分
        # original = Capturer.grab(win=True, region=region, convert=True)
        original = Image.read(rf'image/test/1668496773254551600.png')
        img = Image.gray(original)
        # cv2.imshow('res', img)
        # cv2.waitKey(0)
        # cv2.destroyAllWindows()
        img = Image.binary(img, adaptive=True, block=9)
        # cv2.imwrite(rf'image/result/{time.time_ns()}.jpg', img)
        # 识别存在的武器序号
        indexes = []
        one = data.get(cfg.one)
        two = data.get(cfg.two)
        counter = 0
        for point in one.get(1):
            # print(point, img[point])
            if img[point] == 0:
                counter += 1
        if counter == len(one.get(1)):
            indexes.append(1)
            counter = 0
            # print()
            for point in two.get(2):
                # print(point, img[point])
                if img[point] == 0:
                    counter += 1
            if counter == len(two.get(2)):
                indexes.append(2)
        else:
            counter = 0
            # print()
            for point in one.get(2):
                # print(point, img[point])
                if img[point] == 0:
                    counter += 1
            if counter == len(one.get(2)):
                indexes.append(2)
        print(indexes)
        # 根据识别到的武器序号判断激活的武器
        if len(indexes) == 0:
            return None
        if len(indexes) == 1:
            # 识别1号位是否激活
            active = self.active(original, one)
            return indexes[0] if active else None
        if len(indexes) == 2:
            # 识别1号位是否激活
            active = self.active(original, one)
            if active:
                return 1
            else:
                # 判断2号位是否激活
                active = self.active(original, two)
                return 2 if active else None
        # 其他情况
        return None

    """
    ---------- ---------- ---------- ---------- ----------
    """

    def name(self, img):
        """
        识别武器名称, 入参图片需为 OpenCV 格式
        """
        # 截图灰度化
        img = Image.gray(img)
        # 截图二值化
        img = Image.binary(img, threshold=254)
        # 数纯白色点
        height, width = img.shape
        counter = 0
        for row in range(0, height):
            for col in range(0, width):
                if 255 == img[row, col]:
                    counter += 1
        return self.std_names.get(counter)

    def attachment(self, imgs, img):
        """
        识别武器配件, 入参图片需为 OpenCV 格式
        """
        img = Image.convert(img, gray=True, binary=self.binary, remove=self.remove)
        for name, standard in imgs:
            similarity = Image.similarity(standard, img)
            # print(similarity, name)
            if similarity > 0.925:
                return name
        return None

    def recognize(self, img, config):
        """
        传入武器大图和识别名称配件的配置项, 返回识别到的武器. 入参图片需为 OpenCV 格式
        """
        # 判断武器是否存在
        exist = np.mean(img[config.get(cfg.point)]) == 255  # 取 BGR 列表的均值, 判断是不是纯白色
        if not exist:
            return None
        # 武器存在, 先识别名称
        name = self.name(Image.cut(img, config.get(cfg.name)))
        if not name:
            return None
        # 识别出武器名称后再识别配件
        index = config.get(cfg.index)
        sight = self.attachment(self.std_imgs_sight_1 if index == 1 else self.std_imgs_sight_2, Image.cut(img, config.get(cfg.sight)))
        muzzle = self.attachment(self.std_imgs_muzzle, Image.cut(img, config.get(cfg.muzzle)))
        foregrip = self.attachment(self.std_imgs_foregrip, Image.cut(img, config.get(cfg.foregrip)))
        stock = self.attachment(self.std_imgs_stock, Image.cut(img, config.get(cfg.stock)))
        return Weapon(name, sight, muzzle, foregrip, stock)

    def active(self, img, config):
        region = config.get(cfg.region)
        img = Image.cut(img, region)
        img = Image.gray(img, True)
        # img = Image.binary(img, adaptive=True, block=9)
        # img = Image.binary(img, threshold=230)
        # cv2.imwrite(rf'image/result/{time.time_ns()}.jpg', img)

        # cv2.imshow('res', img)
        # cv2.waitKey(0)
        # cv2.destroyAllWindows()

        # 方式1, 找最多的颜色, 不太行
        data = {}
        height, width = img.shape
        for row in range(0, height):
            for col in range(0, width):
                counter = data.get(img[row, col])
                if counter:
                    counter += 1
                else:
                    counter = 1
                data[img[row, col]] = counter
        key = -1
        counter = 0
        for k, v in data.items():
            if v > counter:
                key = k
                counter = v
        print(f'最多的颜色:', key, counter)

        # 方式2, 找大于某值的颜色数
        height, width = img.shape
        counter = 0
        for row in range(0, height):
            for col in range(0, width):
                if img[row, col] > 210:
                    counter += 1
        print(f'大于某值数:', counter)

        # 方式3, 最大颜色值
        value = -1
        for row in range(0, height):
            for col in range(0, width):
                if img[row, col] > value:
                    value = img[row, col]
        print(f'最大颜色值:', value)

        return False

pubg.py

import ctypes
import multiprocessing
import time
from multiprocessing import Process
import pynput  # pip install pynput
import winsound

from toolkit import Pubg, Timer

end = 'end'
tab = 'tab'
ads = 'ads'
fire = 'fire'
temp = 'temp'
debug = 'debug'
index = 'index'
right = 'right'
switch = 'switch'
weapon = 'weapon'
weapons = 'weapons'
attitude = 'attitude'
firemode = 'firemode'
recognize = 'recognize'
timestamp = 'timestamp'
init = {
    end: False,  # 退出标记
    switch: True,  # 压枪开关
    tab: 0,  # 背包检测信号, 非0触发检测. Tab键触发修改, 用于检测背包界面中的武器信息
    weapons: None,  # 背包界面中的两把主武器信息, 字典格式, {1:武器1, 2:武器2}
    index: 0,  # 激活检测信号, 非0触发检测. 鼠标滚轮滚动/1/2/3/4/5/G(切雷)/F(落地捡枪)/X(收起武器)/Tab(调整位置)/ 等按键触发修改
    weapon: None,  # 当前持有的主武器
    right: 0,  # 右键检测信号, 非0触发检测. 右键触发修改, 包括当前的角色姿态, 当前激活的武器, 武器的射击模式
    attitude: None,  # 姿态, stand:站, squat:蹲, prone:爬, 开火时检测(开火时要按右键,按右键后会出现姿态标识)
    firemode: None,  # 射击模式, auto:全自动, semi:半自动(点射), only:单发
    timestamp: None,  # 按下左键开火时的时间戳
    fire: False,  # 开火状态
    ads: 2,  # 基准倍数
    debug: False,  # 调试模式开关
    temp: None,  # 调试下压力度数据使用
}


def mouse(data):

    def down(x, y, button, pressed):
        if Pubg.game():
            if button == pynput.mouse.Button.x1:
                # 侧下键
                if pressed:
                    # 压枪开关
                    data[switch] = not data.get(switch)
                    winsound.Beep(800 if data[switch] else 400, 200)
            elif button == pynput.mouse.Button.left:
                data[fire] = pressed
                if pressed:
                    data[timestamp] = time.time_ns()
            elif button == pynput.mouse.Button.right:
                if pressed:
                    data[right] = 1
            elif button == pynput.mouse.Button.x2:  # todo 调试弹道
                if pressed and data[debug]:
                    with open('debug', 'r') as file:
                        try:
                            exec(file.read())
                            print(data[temp])
                        except Exception as e:
                            print(e.args)
                            print(str(e))
                            print(repr(e))

    def scroll(x, y, dx, dy):
        if Pubg.game():
            data[index] = 1

    with pynput.mouse.Listener(on_click=down, on_scroll=scroll) as m:
        m.join()


def keyboard(data):

    def release(key):
        if key == pynput.keyboard.Key.end:
            # 结束程序
            winsound.Beep(400, 200)
            data[end] = True
            return False
        if Pubg.game():
            if key == pynput.keyboard.Key.tab:
                # tab: 背包检测与武器识别的状态
                # 0: 默认状态
                # 1: 背包检测中
                # 2: 武器识别中
                # 3: 等待关闭背包
                if data[tab] == 0:  # 等待打开背包
                    data[tab] = 1
                elif data[tab] == 1:  # 背包检测中, 中止检测, 恢复默认状态(循环中会有状态机式的状态感知)
                    data[tab] = 0
                    data[index] = 1
                elif data[tab] == 2:  # 武器识别中, 中止识别, 恢复默认状态
                    data[tab] = 0
                    data[index] = 1
                elif data[tab] == 3:  # 武器已识别, 等待关闭背包, 恢复默认状态
                    data[tab] = 0
                    data[index] = 1
            elif key == pynput.keyboard.KeyCode.from_char('1'):
                data[index] = 1
                # todo
                if data[weapons] is not None and data[weapons].get(1) is not None:
                    data[weapon] = data[weapons].get(1)
            elif key == pynput.keyboard.KeyCode.from_char('2'):
                data[index] = 1
                # todo
                if data[weapons] is not None and data[weapons].get(2) is not None:
                    data[weapon] = data[weapons].get(2)
            elif key == pynput.keyboard.KeyCode.from_char('3'):
                data[index] = 1
            elif key == pynput.keyboard.KeyCode.from_char('4'):
                data[index] = 1
            elif key == pynput.keyboard.KeyCode.from_char('5'):
                data[index] = 1
            elif key == pynput.keyboard.KeyCode.from_char('g'):
                data[index] = 1
            elif key == pynput.keyboard.KeyCode.from_char('f'):
                data[index] = 1
            elif key == pynput.keyboard.KeyCode.from_char('x'):
                data[index] = 1

    with pynput.keyboard.Listener(on_release=release) as k:
        k.join()


def suppress(data):

    try:
        driver = ctypes.CDLL('logitech.driver.dll')
        ok = driver.device_open() == 1  # 该驱动每个进程可打开一个实例
        if not ok:
            print('Error, GHUB or LGS driver not found')
    except FileNotFoundError:
        print('Error, DLL file not found')

    def move(x, y):
        if ok:
            driver.moveR(x, y, True)

    pubg = Pubg()
    winsound.Beep(800, 200)

    counter = 0  # 检测计数器, 防止因不正常状态导致背包检测和武器识别陷入死循环, 10个循环内没有结果就会强制退出

    def show():
        print('==========')
        if data[weapons]:
            for k, v in data[weapons].items():
                print(f'{k}: {v}')
        print(f'index: {data[index]}, {data[attitude]}, {data[firemode]}')

    while True:

        if data.get(end):  # 退出程序
            break
        if not data.get(switch):
            data[tab] = 0  # 开关关闭时, 每次循环都会重置背包检测信号
            continue
        if not pubg.game():  # 如果不在游戏中
            continue
        if data[tab] == 1:  # 背包界面检测
            counter += 1
            if counter >= 10:  # 举例: 开着背包的时候, 启动辅助并打开开关, 按Tab键关闭背包, 触发辅助更新为状态1, 因为背包已关闭不可能判定是在背包界面, 导致卡状态1
                data[tab] = 0
                counter = 0
            if pubg.backpack() and data[tab] == 1:  # 背包界面检测
                data[tab] = 2
                counter = 0
                continue
        if data[tab] == 2:  # 背包中武器识别
            counter += 1
            if counter >= 10:
                data[tab] = 0
                counter = 0
            first, second = pubg.weapon()  # 背包中武器识别
            if data[tab] == 2:
                data[tab] = 3
                counter = 0
                winsound.Beep(600, 200)  # 通知武器识别结束
                data[weapons] = {
                    1: first,
                    2: second,
                }
                show()
                continue
        if data[index] != 0:  # 检测当前激活的是几号武器
            data[index] = 0
            time.sleep(0.2)  # 防止UI还没有改变
            if not data[weapons]:  # 如果还没有识别过背包中的武器, 则不检测当前激活的是几号武器
                continue
            count = 0  # 如果识别过背包中的武器, 但识别到的都是 None, 则不检测当前激活的是几号武器
            for key, value in data[weapons].items():
                if not value:
                    count += 1
            if count == 0:
                continue
            # data[weapon] = data[weapons].get(pubg.index())  # 检测当前激活的是几号武器  # todo
            show()
            continue
        if data[right] != 0:  # 右键检测
            data[right] = 0
            time.sleep(0.2)  # 防止UI还没有改变
            data[attitude] = pubg.attitude()  # 检测角色姿态
            data[firemode] = pubg.firemode()  # 检测射击模式
            show()
        if data[fire]:  # 开火检测, 默认开火前一定按下了右键, 做了右键检测
            gun = data[weapon]
            if gun is None:  # 如果不确定当前武器则不压枪
                print('武器不确定')
                continue
            if gun.suppress is False:  # 如果当前武器不支持压枪
                print('武器不支持')
                continue
            data[firemode] = pubg.firemode()
            if data[firemode] != 'auto':  # 全自动才压枪(突击步枪/冲锋枪). 这里有隐藏效果,不在对局中/未持枪/背包界面等情景下返回值都是None
                print('武器非自动')
                continue
            if not pubg.bullet():  # 如果弹夹空了
                print('武器弹夹空')
                continue
            print('----------')
            cost = time.time_ns() - data[timestamp]  # 开火时长
            base = gun.interval * 1_000_000  # 基准间隔时间转纳秒
            i = cost // base  # 本回合的压枪力度数值索引
            distance = int(data[ads] * gun.ballistic[i] * gun.factor * gun.attitude(data[attitude]))  # 下移距离
            distance = int(data[ads] * gun.ballistic[i] * data[temp]) if data[temp] else distance  # 下移距离, 去除武器因子和姿态因子的影响, 用于测试当前弹道力度下某单一因素的影响因子值(比如测不同握把的影响)
            print(f'开火时长:{Timer.cost(cost)}, {i}, 压制力度:{distance}, 武器因子:{gun.factor}, 姿态因子:{gun.attitude(data[attitude])}')
            cost = time.time_ns() - data[timestamp]
            left = base - cost % base  # 本回合剩余时间纳秒
            mean = left / distance  # 平缓压枪每个实际力度的延时
            for i in range(0, distance):
                begin = time.perf_counter_ns()
                while time.perf_counter_ns() - begin < mean:
                    pass
                move(0, 1)


if __name__ == '__main__':
    multiprocessing.freeze_support()
    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()
    pm.terminate()

分析测试等源码见 GitHub 工程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/51611.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt通过ODBC连接openGauss数据库

文章目录前言一、Qt链接测试1.测试代码2.测试效果二、环境搭建1.通过ODBC连接openGauss数据库2. 环境测试三、Qt通过ODBC操作数据库1.查询数据1.插入数据3.更新数据总结前言 本文就介绍了Qt通过ODBC连接opengauss数据库的基础内容。 一、Qt链接测试 1.测试代码 在.pro文件中…

java(面向对象)的23种设计模式(11)——观察者模式

一、定义 观察者模式&#xff1a;指多个对象间存在一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。 换种说法&#xff0c;定义两种对象&#xff0c;观察者和目标对象&#xff0c;多个观察者同时监听一个目标对…

高等数学基础概念的Python开发实现

一般的数学算式math函数库就可以解决了&#xff0c;如果是涉及到高等数学极限&#xff0c;微积分等知识&#xff0c;就需要用到sympy科学计算库&#xff0c;它是专门用来解决数学的运算问题的。 Sympy是一个符号计算的Python库。它的目标是成为一个全功能的计算机代数系统&…

你的团队是王者还是青铜(上)

&#xff08;图片来源&#xff1a;https://unsplash.com/photos/RxOrX1iW15A&#xff09; 4月18日早上9点30分&#xff0c;团队跟着大屏计时器整齐地喊出倒计时&#xff0c;“五、四、三、二、一”&#xff0c;Tech Lead 强哥和 PO 小楠相对看了一眼&#xff0c;一起按下了eart…

双端 Diff 算法原理解析及 snabbdom 简单实现

一、准备工作 先找个放猪的容器canvas,这里宽设置了1200&#xff0c;高设置了600 <canvas width"1200" height"600" id"canvas">当前浏览器不支持canvas元素</canvas> 然后获取它进行操作 const canvas document.getElementById(…

让人头皮发麻的Android 性能优化版块,这样简单就学会了?

对现如今的Android 开发们来讲&#xff0c;不管是在面试还是日常工作当中&#xff0c;性能优化 都是一个绕不开的难题。 以下这些场景&#xff0c;大家或多或少都有遇到过&#xff1a; 1. 当你很努力地优化了应用的性能后&#xff0c;用户依然不断抱怨应用卡顿、启动速度慢等…

005. 组合总和 II

1.题目链接&#xff1a; 40. 组合总和 II 2.解题思路&#xff1a; 树层&#xff1a;同层遍历 树枝&#xff1a;递归遍历 2.1.题目要求&#xff1a; 给定一个数组 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candi…

6.终于了解volatile的原理和使用方法了

1.volatile能保证可见性 先说个案例&#xff1a; 有两个线程A和B&#xff0c;有一个公共的 boolean flag 标记位&#xff0c;最开始赋值为 true&#xff1b;B线程循环&#xff0c;根据这个flag来进行执行或者退出&#xff1b;这时线程A把flag改成false这个时候希望线程B看到变…

python安全工具开发笔记(三)——python 多线程

一、Python线程和进程 进程 进程是程序的一次执行。每个进程都有自己的地址空间、内存、数据栈及其它记录其运行轨迹的辅助数据。 线程 所有的线程运行在同一个进程当中&#xff0c;共享相同的运行环境。线程有开始顺序执行和结束三个部分。 帮助理解&#xff1a; 1、计算…

vs2019+Qt 使用 Qlabel 在界面上显示图像及显示失真问题

在使用 Qt 设计界面时&#xff0c;通常会涉及到在界面上显示图片的问题&#xff0c;而要在界面上显示图片需要使用控件 Qlabel 和 函数 QImage &#xff0c;下面对控件和函数逐一做出介绍&#xff01;&#xff01;&#xff01; 一、Qlabel 常见成员方法 1、setText(const QSt…

全国批发市场情况萧条,进销存系统或是业务转机

如今批发市场情况大不如前&#xff0c;越发惨淡&#xff0c;令人不禁扼腕叹息。让我们深入批发行业&#xff0c;撇开大环境因素&#xff0c;来究竟发现什么是导致批发市场的萧条现状的原因。 1、物流快速发展&#xff0c;失去地域优势 在90年代初&#xff0c;各地交通不便&…

idea相关配置-----java

导入项目 打开项目src的上一层目录即可 导入之后如果可以正常运行就不用看下面操作了&#xff0c; 如果不能运行可以参考下面内容 配置 1.jdk配置 2.添加项目jar包&#xff0c;然后应用 配置完成了 常用快捷键 快捷代码 sout 输出 forr 创建倒序 for循环 fori 创建 for循环…

国外Windows主机的特点

虚拟主机是一项为用户提供在线系统的服务&#xff0c;用于存储信息、图像、视频或其他可以通过互联网轻松访问的文件。而Windows虚拟主机是其中的一个类别&#xff0c;使用这款主机的用户需要和其他共享一个服务器——包括物理服务器和软件应用程序。目前&#xff0c;大多数应用…

Cesium For Unity3d 最新实践流程-2022-12-01

目录 Cesium-Unity3d 最新实践流程 一、前言 二、实践 1、Unity 安装 2、Cesium for Unity 下载 3、打开项目 4、编辑、运行项目 4、效果 Cesium For Unity3d 最新实践流程 一、前言 2022年11月30日晚11点30分&#xff0c;Cesium for Unity 开源插件预览版发布&#…

7.axios的基本使用

Axios是专注于网络数据请求的库&#xff0c;比jQuery更轻量&#xff0c;项目地址 下载解压后在dist中可以找到axios.js&#xff0c;在html文件中引用它就好了 目录 1 GET请求 2 POST请求 3 axios() 1 GET请求 服务是两个数相加 返回的res对象有六个属性&#xff0c;你…

【Python】基础语法1(常量与表达式、变量和类型、注释、输入输出、运算符)

文章目录1、常量与表达式2. 变量和类型2.1 变量是什么2.2 变量的语法2.3 变量的类型2.3.1整数2.3.2 浮点数2.3.3 字符串2.3.4 布尔2.3.5 其他2.4 变量类型的意义2.5 动态类型特性3. 注释3.1 注释的语法3.2 注释的规范4. 输入输出4.1 通过控制台输出4.2 通过控制台输入5. 运算符…

java - 数据结构,时间复杂度和空间复杂度

一、算法效率 算法效率分析分为两种&#xff1a;第一种是时间效率&#xff0c;第二种是空间效率。 时间效率被称为时间复杂度&#xff0c;而空间效率被称作空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度&#xff0c;而空间复杂度主要衡量一个算法所需要的额外空间&am…

子不语发生工商变更:注册资本增至3000万元,预计全年净利润下滑

近日&#xff0c;浙江子不语电子商务有限公司&#xff08;下称“子不语”&#xff09;发生工商变更&#xff0c;其中注册资本由2600万元增至3000万元。据天眼查信息显示&#xff0c;子不语的全资股东为ZIBUYU INTERNATIONAL LIMITED&#xff0c;法定代表人为华丙如。 据了解&am…

【前端验证】验证自动化脚本的最后一块拼图补全——gen_tb

我们的目标是┏ (゜ω゜)=☞芯片前端全栈工程师~喵! 前言 在完成了 【芯片前端】可能是定向验证的巅峰之作——auto_testbench_尼德兰的喵的博客-CSDN博客 【python脚本】用于生成简单握手接口与自测环境的gen_uvm_agent脚本_尼德兰的喵的博客-CSDN博客 两个工具之后,对于…

[附源码]计算机毕业设计springboot体育器材及场地管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…