Article directory
- Environmental preparation
-
- Manipulate the keyboard and mouse
- Keyboard and mouse monitoring
- weapon identification
-
- How to easily and efficiently determine whether you are in-game or not
- How to simply and efficiently judge the backpack status without weapon/weapon 1/weapon 2
- How to easily and efficiently determine the weapon bullet type light/heavy/energy/sniper/shot/airdrop
- How to easily and efficiently determine the name of a weapon
- How to easily and efficiently judge the weapon mode fully automatic/burst/single
- How to easily and efficiently determine whether you have a weapon
- How to easily and efficiently determine whether the magazine is empty
- When to trigger recognition
- a few details
- pressure gun ideas
- Organizing data
- The first stage implementation can automatically identify all weapons
- The second stage realizes that the corresponding gun shaking parameters can be automatically used to execute the pressure gun
- The third stage realizes the abandonment of the gun shaking technique and the conventional recoil offset method (in optimization)
- The fourth stage realizes AI target detection, moves the mouse, and bids farewell to the pressure gun completely
This article is the study and practice of the following reference articles
[Original] FPS game automatic firearm recognition + pressure gun (taking PUBG as an example)
[Reprint] FPS game automatic firearm recognition + pressure gun (taking PUBG as an example)
Environmental preparation
conda create -n apex python=3.9
Manipulate the keyboard and mouse
Since PUBG blocks other mouse input outside the hardware driver, we cannot directly control the mouse operation in the game through the py script. In order to realize the mouse down in the game, I used the Logitech mouse driver (ghub), and py passed the command operation to ghub by calling the link library file of ghub, and finally realized the use of hardware-driven mouse commands to input to the game, thus Bypass the game's mouse input restrictions. It is worth mentioning that we only pass the instructions to the Logitech driver through the py code calling the interface of the link library, which has nothing to do with the actual mouse used, so even if the user uses Razer, Zhuowei, Shuangfeiyan, etc. The mouse, has no effect on the code below.
Driver installation link library loading code preparation and out-of-game testing
The Logitech driver uses LGS_9.02.65_X64 (please find the resources to install by yourself, the new version of the Logitech driver on the official website does not find the corresponding link library file). The link library file can be found in the project link. Below is the code to load the link library.
Logitech drivers are divided into LGS (old) and GHub (new), the specified version of LGS driver must be installed (if GHub is installed, it may need to be uninstalled), otherwise it will either report that it is not installed, or the initialization is successful but the call is invalid.
try:
gm = CDLL(r'./mouse.device.lgs.dll')
gmok = gm.device_open() == 1
if not gmok:
print('未安装ghub或者lgs驱动!!!')
else:
print('初始化成功!')
except FileNotFoundError:
print('缺少文件')
After the driver is installed, it will take effect immediately without restarting the computer. Unfortunately, there is no corresponding documentation for the methods in this dll file, so we can only guess the parameters.
toolkit.py
import time
from ctypes import CDLL
import win32api # conda install pywin32
try:
driver = CDLL(r'mouse.device.lgs.dll') # 在Python的string前面加上‘r’, 是为了告诉编译器这个string是个raw string(原始字符串),不要转义backslash(反斜杠) '\'
ok = driver.device_open() == 1
if not ok:
print('初始化失败, 未安装lgs/ghub驱动')
except FileNotFoundError:
print('初始化失败, 缺少文件')
class Mouse:
@staticmethod
def move(x, y, absolute=False):
if ok:
mx, my = x, y
if absolute:
ox, oy = win32api.GetCursorPos()
mx = x - ox
my = y - oy
driver.moveR(mx, my, True)
@staticmethod
def down(code):
if ok:
driver.mouse_down(code)
@staticmethod
def up(code):
if ok:
driver.mouse_up(code)
@staticmethod
def click(code):
"""
:param code: 1:左键, 2:中键, 3:右键, 4:侧下键, 5:侧上键, 6:DPI键
:return:
"""
if ok:
driver.mouse_down(code)
driver.mouse_up(code)
class Keyboard:
@staticmethod
def press(code):
if ok:
driver.key_down(code)
@staticmethod
def release(code):
if ok:
driver.key_up(code)
@staticmethod
def click(code):
"""
:param code: 'a'-'z':A键-Z键, '0'-'9':0-9, 其他的没猜出来
:return:
"""
if ok:
driver.key_down(code)
driver.key_up(code)
in-game testing
After trying it in the game, it works, but it is not allowed, guess it may be related to the mouse sensitivity/FOV in the game, etc.
from toolkit import Mouse
import pynput # conda install pynput
def onClick(x, y, button, pressed):
if not pressed:
if pynput.mouse.Button.x2 == button:
Mouse.move(100, 100)
mouseListener = pynput.mouse.Listener(on_click=onClick)
mouseListener.start()
mouseListener.join()
Keyboard and mouse monitoring
As mentioned earlier, in order to realize the pressure gun, it is necessary to identify various accessories and states. So before writing the recognition function, we need to solve the problem of when to recognize. It is undoubtedly a huge overhead to identify the continuous detection of multi-threaded/multi-process, so it is necessary to monitor the status of the keyboard and mouse. A specific corresponding identification request is triggered only when a specific key is pressed.
The hook I use here is Pynput, and other libraries that can be used are Pyhook3
def onClick(x, y, button, pressed):
print(f'button {
button} {
"pressed" if pressed else "released"} at ({
x},{
y})')
if pynput.mouse.Button.left == button:
return False # 正常不要返回False, 这样会结束监听并停止监听线程, 在关闭程序前返回False就好了
listener = pynput.mouse.Listener(on_click=onClick)
listener.start()
def onRelease(key):
print(f'{
key} released')
if key == pynput.keyboard.Key.end:
return False # 正常不要返回False, 这样会结束监听并停止监听线程, 在关闭程序前返回False就好了
listener = pynput.keyboard.Listener(on_release=onRelease)
listener.start()
Note that when debugging the callback method, don't break the point, don't break the point, don't break the point, this will block the IO and cause the mouse and keyboard to fail.
The functions of on_press and on_release (on_key_press, on_key_release) are bound in the Listener. When they return False, they end the monitoring. The same is true for the mouse monitoring functions below, so don’t just return False.
The special keys of the keyboard are written in keyboard.Key.tab, and the ordinary keys are written in keyboard.KeyCode.from_char('c').
Some keys do not know how to write, you can print(key)
check spelling
There is a very pit here. The parameters of on_press and on_release can only have one key, and this key is the key pressed on the corresponding keyboard. But this is not enough to meet our needs, because we should modify the semaphore inside the hook function when the specified button is pressed, but due to the limitation of parameters, we cannot pass the semaphore into the function, here I I also thought about it for a long time, and finally thought of using the nested function to solve this problem.
Also, the hook function itself is blocking. That is to say, during the execution of the hook function, the user's normal keyboard/mouse operations cannot be input. Therefore, the hook function must be written as a limited operation (that is, the code with O(1) time complexity), which means that the time overhead such as the identification of accessories in the backpack and firearms, and the mouse pressure gun mentioned below is relatively large. Or long-duration operations are not suitable for writing in hook functions. This also explains why when Tab (opening the backpack) and the left mouse button are detected, the semaphore is only changed, and then these tasks are left to other processes to do.
weapon identification
How to easily and efficiently determine whether you are in-game or not
First, determine whether the game window is at the forefront, and then determine whether the gun interface is being held in the game.
Find a few feature points to judge the color, the upper left corner of the health bar and the lower left corner of the survival item box
Generally, the point that can be used for color picking, its color RGB is the same, the color of this point is very stable
I originally thought that it would take no more than 1ms to select a color on the screen, but I never expected that it would take 1-10ms to select a color, which is extremely inefficient, and there is no other elegant method.
How to simply and efficiently judge the backpack status without weapon/weapon 1/weapon 2
Look at the color of the part circled in red on the weapon frame, gray means no weapon, different colors up and down indicate the use of the No. 2 weapon, and the same color up and down means that the No. 1 weapon is used
How to easily and efficiently determine the weapon bullet type light/heavy/energy/sniper/shot/airdrop
Because weapons of different bullet types have different border colors. So it can be put together with the above, and the same point can directly determine the status of the backpack and the type of bullets
How to easily and efficiently determine the name of a weapon
On the basis of classification, determine the position to check the color (position 1/position 2) by the status of the backpack, narrow the judgment range by the weapon bullet category, and find a pure white point on the name of each weapon to ensure this Point only this weapon is pure white, and then compare them one by one
How to easily and efficiently judge the weapon mode fully automatic/burst/single
The only weapons that need to press the gun are fully automatic and semi-automatic. Single shot does not need to press the gun (it is possible to do automatic single shot later, which will be considered when the time comes), and the gun and sniper do not need to press the gun.
So you need to find a point that can distinguish the three modes (the color of this point is different but stable in different modes), and this point cannot be affected by the special marks of peace and triple
At first, I found a point that was not pure white. Later, I found that the color of this point will be affected by the background color, which is not constant. In the end, I gave up the idea of distinguishing the mode with one point, and adopted a stable pure white point. , to ensure that the point is pure white only in this mode, and not pure white in other modes
How to easily and efficiently determine whether you have a weapon
It is impossible to judge for the time being. There is no fixed point that can clearly distinguish between the two situations when the weapon is put away and the weapon is held.
Some weapons can be judged by the [V] mark, because they are incomplete, they will not be used first
You can also set the mark by monitoring and pressing the [3] key (the operation of retracting the weapon), other operations remove the mark, and then read the mark to determine whether you have a weapon, but it is not elegant, so don’t use it first
The current conclusion is that when using a fist, the crosshair is a large square crosshair, and when using a weapon, it is a round crosshair. But using a fist does not mean not holding a weapon
How to easily and efficiently determine whether the magazine is empty
The number of bullets in the magazine is mostly two digits (LSTAR may be three digits), so just confirm that the tens digit is not 0, it can be considered not empty, the tens digit is 0 and the ones digit is 0, it can be considered empty
- The tens point is in the middle of the number, 1-9 are pure white, 0 is gray. Note that this gray is not a fixed color, the color will change as the background changes
- The point of the one's digit, at the leftmost end of the slash in the middle of the number 0, this point is pure white, and when other 1-9, this point is not pure white
When to trigger recognition
- Press the right button of the mouse to identify the weapon. It does not conflict with the original button function in the game
- 1/2/3/E/V/R/Tab/Esc/Alt key release, identify weapon
- Home key release, toggle switch
- end key release, end the program
a few details
- Through testing, it is found that the firing interval of all weapons is greater than 50 milliseconds, so when pressing the gun, you can do some operations within 50 milliseconds, such as judging whether the magazine is empty, to avoid triggering the pressing gun
pressure gun ideas
There are 3 ideas for apex's pressure gun, because the ballistics of apex's different weapons seem to be fixed, there is no random value?, and other games too??
- The left and right shaking counteracts the horizontal recoil, and the pull-down counteracts the vertical recoil. This method is simple, but the picture will shake and the effect is not very good
- Test the recoil data of the weapon under different conditions according to the weapon accessories, etc., and then do the reverse offset.
You can use a tricky way to only do the reverse offset without the accessories, and save the trouble of finding accessories.
This method is too difficult and too troublesome Yes, but if done well, it's basically a line, and it's outrageously strong - There is also the very popular AI target detection (yolov5), which I also tried to do. The environment was set up, but it got stuck in the middle. First, after all, python is an interest, many basics are not in place, and relevant professional knowledge is even blank. For reference The content is also uneven, which makes the parameters of detection and training very vague. The second is the collection of data sets. I found some on the Internet and made some myself, but there is still only a little bit. Don't worry, look for it slowly. If you want to get a good effect, you need thousands of pictures...
Organizing data
Weapon data, grouped by bullet type, each member in the group specifies the serial number, name, gun pressure parameters and other information
Configuration data, grouped by resolution, and then classified by whether it is in the game, whether there is a weapon, weapon location, weapon bullet type, weapon index, etc.
Signal data, program runtime, inter-process thread communication
The first stage implementation can automatically identify all weapons
At present, after testing, a wave of recognition is about 60 to 70 milliseconds, and it will not exceed 100 milliseconds at most. The main time is spent on the color selection function (1-10ms), and the performance is sufficient.
My configuration: AMD R7 2700x, Nvidia RTX 2080, 3440*1440 resolution
cfg.py
mode = 'mode'
name = 'name'
game = 'game'
data = 'data'
pack = 'pack' # 背包
color = 'color'
point = 'point'
index = 'index'
bullet = 'bullet' # 子弹
differ = 'differ'
positive = 'positive' # 肯定的
negative = 'negative' # 否定的
# 检测数据
detect = {
"3440:1440": {
game: [ # 判断是否在游戏中
{
point: (236, 1344), # 点的坐标, 血条左上角
color: 0x00FFFFFF # 点的颜色, 255, 255, 255
},
{
point: (2692, 1372), # 生存物品右下角
color: 0x959595 # 149, 149, 149
}
],
pack: {
# 背包状态, 有无武器, 选择的武器
point: (2900, 1372), # 两把武器时, 1号武器上面边框分界线的上半部分, y+1 就是1号武器上面边框分界线的下半部分
color: 0x808080, # 无武器时, 灰色, 128, 128, 128
'0x447bb4': 1, # 轻型弹药武器, 子弹类型: 1/2/3/4/5/6/None(无武器)
'0x839b54': 2, # 重型弹药武器
'0x3da084': 3, # 能量弹药武器
'0xce5f6e': 4, # 狙击弹药武器
'0xf339b': 5, # 霰弹枪弹药武器
'0x5302ff': 6, # 空投武器
},
mode: {
# 武器模式, 全自动/半自动/单发/其他
point: (3148, 1349),
'0xf8f8f8': 1, # 全自动
'0xfefefe': 2 # 半自动
},
name: {
# 武器名称判断
color: 0x00FFFFFF,
'1': {
# 1号武器
'1': [ # 轻型弹药武器
(2959, 1386), # 1: RE-45 自动手枪
(2970, 1385), # 2: 转换者冲锋枪
(2972, 1386), # 3: R-301 卡宾枪
(2976, 1386), # 4: R-99 冲锋枪
(2980, 1386), # 5: P2020 手枪
(2980, 1384), # 6: 喷火轻机枪
(2987, 1387), # 7: G7 侦查枪
(3015, 1386), # 8: CAR (轻型弹药)
],
'2': [ # 重型弹药武器
(2957, 1385), # 1: 赫姆洛克突击步枪
(2982, 1385), # 2: 猎兽冲锋枪
(2990, 1393), # 3: 平行步枪
(3004, 1386), # 4: 30-30
(3015, 1386), # 5: CAR (重型弹药)
],
'3': [ # 能量弹药武器
(2955, 1386), # 1: L-STAR能量机枪
(2970, 1384), # 2: 三重式狙击枪
(2981, 1385), # 3: 电能冲锋枪
(2986, 1384), # 4: 专注轻机枪
(2980, 1384), # 5: 哈沃克步枪
],
'4': [ # 狙击弹药武器
(2969, 1395), # 1: 哨兵狙击步枪
(2999, 1382), # 2: 充能步枪
(2992, 1385), # 3: 辅助手枪
(3016, 1383), # 4: 长弓
],
'5': [ # 霰弹枪弹药武器
(2957, 1384), # 1: 和平捍卫者霰弹枪
(2995, 1382), # 2: 莫桑比克
(3005, 1386), # 3: EVA-8
],
'6': [ # 空投武器
(2958, 1384), # 1: 克雷贝尔狙击枪
(2983, 1384), # 2: 敖犬霰弹枪
(3003, 1383), # 3: 波塞克
(3014, 1383), # 4: 暴走
]
},
'2': {
differ: 195 # 直接用1的坐标, 横坐标右移195就可以了
}
}
},
"2560:1440": {
},
"2560:1080": {
},
"1920:1080": {
}
}
# 武器数据
weapon = {
'1': {
# 轻型弹药武器
'1': {
name: 'RE-45 自动手枪',
},
'2': {
name: '转换者冲锋枪',
},
'3': {
name: 'R-301 卡宾枪',
},
'4': {
name: 'R-99 冲锋枪',
},
'5': {
name: 'P2020 手枪',
},
'6': {
name: '喷火轻机枪',
},
'7': {
name: 'G7 侦查枪',
},
'8': {
name: 'CAR (轻型弹药)',
}
},
'2': {
# 重型弹药武器
'1': {
name: '赫姆洛克突击步枪',
},
'2': {
name: '猎兽冲锋枪',
},
'3': {
name: '平行步枪',
},
'4': {
name: '30-30',
},
'5': {
name: 'CAR (重型弹药)',
}
},
'3': {
# 能量弹药武器
'1': {
name: 'L-STAR能量机枪',
},
'2': {
name: '三重式狙击枪',
},
'3': {
name: '电能冲锋枪',
},
'4': {
name: '专注轻机枪',
},
'5': {
name: '哈沃克步枪',
},
},
'4': {
# 狙击弹药武器
'1': {
name: '哨兵狙击步枪',
},
'2': {
name: '充能步枪',
},
'3': {
name: '辅助手枪',
},
'4': {
name: '长弓',
},
},
'5': {
# 霰弹弹药武器
'1': {
name: '和平捍卫者霰弹枪',
},
'2': {
name: '莫桑比克',
},
'3': {
name: 'EVA-8',
},
},
'6': {
# 空投武器
'1': {
name: '克雷贝尔狙击枪',
},
'2': {
name: '敖犬霰弹枪',
},
'3': {
name: '波塞克',
},
'4': {
name: '暴走',
},
}
}
toolkit.py
import mss # pip install mss
import ctypes
from ctypes import CDLL
import cfg
from cfg import detect, weapon
# 全局 dll
user32 = ctypes.windll.user32
gdi32 = ctypes.windll.gdi32
hdc = user32.GetDC(None)
try:
driver = CDLL(r'mouse.device.lgs.dll') # 在Python的string前面加上‘r’, 是为了告诉编译器这个string是个raw string(原始字符串),不要转义backslash(反斜杠) '\'
ok = driver.device_open() == 1
if not ok:
print('初始化失败, 未安装lgs/ghub驱动')
except FileNotFoundError:
print('初始化失败, 缺少文件')
class Mouse:
@staticmethod
def point():
return user32.GetCursorPos()
@staticmethod
def move(x, y, absolute=False):
if ok:
mx, my = x, y
if absolute:
ox, oy = user32.GetCursorPos()
mx = x - ox
my = y - oy
driver.moveR(mx, my, True)
@staticmethod
def moveHumanoid(x, y, absolute=False):
"""
仿真移动(还没做好)
"""
if ok:
ox, oy = user32.GetCursorPos() # 原鼠标位置
mx, my = x, y # 相对移动距离
if absolute:
mx = x - ox
my = y - oy
tx, ty = ox + mx, oy + my
print(f'({
ox},{
oy}), ({
tx},{
ty}), x:{
mx},y:{
my}')
# 以绝对位置方式移动(防止相对位置丢失精度)
adx, ady = abs(mx), abs(my)
if adx <= ady:
# 水平方向移动的距离短
for i in range(1, adx):
ix = i if mx > 0 else -i
temp = int(ady / adx * abs(ix))
iy = temp if my > 0 else -temp
Mouse.move(ox + ix, oy + iy, absolute=True)
# time.sleep(0.001)
else:
# 垂直方向移动的距离短
for i in range(1, ady):
iy = i if my > 0 else -i
temp = int(adx / ady * abs(iy))
ix = temp if mx > 0 else -temp
Mouse.move(ox + ix, oy + iy, absolute=True)
# time.sleep(0.001)
@staticmethod
def down(code):
if ok:
driver.mouse_down(code)
@staticmethod
def up(code):
if ok:
driver.mouse_up(code)
@staticmethod
def click(code):
"""
:param code: 1:左键, 2:中键, 3:右键, 4:侧下键, 5:侧上键, 6:DPI键
:return:
"""
if ok:
driver.mouse_down(code)
driver.mouse_up(code)
class Keyboard:
@staticmethod
def press(code):
if ok:
driver.key_down(code)
@staticmethod
def release(code):
if ok:
driver.key_up(code)
@staticmethod
def click(code):
"""
键盘按键函数中,传入的参数采用的是键盘按键对应的键码
:param code: 'a'-'z':A键-Z键, '0'-'9':0-9, 其他的没猜出来
:return:
"""
if ok:
driver.key_down(code)
driver.key_up(code)
class Monitor:
"""
显示器
"""
sct = mss.mss()
@staticmethod
def grab(region):
"""
region: tuple, (left, top, width, height)
pip install mss
"""
left, top, width, height = region
return Monitor.sct.grab(monitor={
'left': left, 'top': top, 'width': width, 'height': height})
@staticmethod
def pixel(x, y):
"""
效率很低且不稳定, 单点检测都要耗时1-10ms
获取颜色, COLORREF 格式, 0x00FFFFFF
结果是int,
可以通过 print(hex(color)) 查看十六进制值
可以通过 print(color == 0x00FFFFFF) 进行颜色判断
"""
# hdc = user32.GetDC(None)
return gdi32.GetPixel(hdc, x, y)
class Resolution:
"""
分辨率
"""
@staticmethod
def display():
"""
显示分辨率
"""
w = user32.GetSystemMetrics(0)
h = user32.GetSystemMetrics(1)
return w, h
@staticmethod
def virtual():
"""
多屏幕组合的虚拟显示器分辨率
"""
w = user32.GetSystemMetrics(78)
h = user32.GetSystemMetrics(79)
return w, h
@staticmethod
def physical():
"""
物理分辨率
"""
# hdc = user32.GetDC(None)
w = gdi32.GetDeviceCaps(hdc, 118)
h = gdi32.GetDeviceCaps(hdc, 117)
return w, h
class Game:
"""
游戏工具
"""
@staticmethod
def game():
"""
是否在游戏内
太耗时了, 所以不能调的多了
"""
w, h = Monitor.Resolution.display()
data = detect.get(f'{
w}:{
h}').get(cfg.game)
for item in data:
x, y = item.get(cfg.point)
if Monitor.pixel(x, y) != item.get(cfg.color):
return False
return True
@staticmethod
def index():
"""
武器索引和子弹类型索引
:return: 武器位索引, 1:1号位, 2:2号位, None:无武器, 拳头(这个暂时无法判断)
子弹类型索引, 1:轻型, 2:重型, 3:能量, 4:狙击, 5:霰弹, 6:空投, None:无武器
"""
w, h = Monitor.Resolution.display()
data = detect.get(f'{
w}:{
h}').get(cfg.pack)
x, y = data.get(cfg.point)
color = Monitor.pixel(x, y)
if data.get(cfg.color) == color:
return None, None
else:
bullet = data.get(hex(color))
return (1, bullet) if color == Monitor.pixel(x, y + 1) else (2, bullet)
@staticmethod
def weapon(index, bullet):
"""
通过武器位和子弹类型识别武器, 参考:config.detect.name
:param index: 武器位, 1:1号位, 2:2号位
:param bullet: 子弹类型, 1:轻型, 2:重型, 3:能量, 4:狙击, 5:霰弹, 6:空投
:return:
"""
w, h = Monitor.Resolution.display()
data = detect.get(f'{
w}:{
h}').get(cfg.name)
color = data.get(cfg.color)
if index == 1:
lst = data.get(str(index)).get(str(bullet))
for i in range(len(lst)):
x, y = lst[i]
if color == Monitor.pixel(x, y):
return i + 1
elif index == 2:
differ = data.get(str(index)).get(cfg.differ)
lst = data.get(str(1)).get(str(bullet))
for i in range(len(lst)):
x, y = lst[i]
if color == Monitor.pixel(x + differ, y):
return i + 1
return None
@staticmethod
def mode():
"""
武器模式
:return: 1:全自动, 2:半自动, None:其他
"""
w, h = Monitor.Resolution.display()
data = detect.get(f'{
w}:{
h}').get(cfg.mode)
x, y = data.get(cfg.point)
color = Monitor.pixel(x, y)
return data.get(hex(color))
@staticmethod
def detect():
"""
决策是否需要压枪, 向信号量写数据
"""
if Game.game() is False:
print('not in game')
return
index, bullet = Game.index()
if (index is None) | (bullet is None):
print('no weapon')
return
if Game.mode() is None:
print('not in full auto or semi auto mode')
return
arms = Game.weapon(index, bullet)
if arms is None:
print('detect weapon failure')
return
# 检测通过, 需要压枪
print(weapon.get(str(bullet)).get(str(arms)).get(cfg.name))
return weapon.get(str(bullet)).get(str(arms)).get(cfg.name)
apex.py
import time
import pynput # conda install pynput
import toolkit
ExitFlag = False
def down(x, y, button, pressed):
global ExitFlag
if ExitFlag:
print(ExitFlag)
return False # 结束监听线程
if pressed: # 按下
if pynput.mouse.Button.right == button:
toolkit.Game.detect()
mouseListener = pynput.mouse.Listener(on_click=down)
mouseListener.start()
def release(key):
if key == pynput.keyboard.Key.end:
print('end')
global ExitFlag
ExitFlag = True
return False
if key == pynput.keyboard.KeyCode.from_char('1'):
toolkit.Game.detect()
elif key == pynput.keyboard.KeyCode.from_char('2'):
toolkit.Game.detect()
elif key == pynput.keyboard.KeyCode.from_char('3'):
toolkit.Game.detect()
elif key == pynput.keyboard.KeyCode.from_char('e'):
toolkit.Game.detect()
elif key == pynput.keyboard.KeyCode.from_char('v'):
toolkit.Game.detect()
keyboardListener = pynput.keyboard.Listener(on_release=release)
keyboardListener.start()
keyboardListener.join()
The second stage realizes that the corresponding gun shaking parameters can be automatically used to execute the pressure gun
- The higher the mouse sensitivity in the game, the easier it is to shake the gun and the effect is better, but if you open it to 5, you will feel a little dizzy
- The higher the mouse sensitivity in the game, the smaller the jittered pixels in the code need to be set. For example, if the sensitivity is 5, the jitter is 2 pixels.
- Shaking the gun can reduce the recoil, but it cannot completely eliminate it, so it needs to be moved in the corresponding direction
At 2.5 sensitivity, 301 uses the following parameters, 20 to 30 meters is ok, 50 meters, and triple the effect will be doubled, which is very bad. The weapon with greater recoil, the first few shots are easy to jump too high, and the next shot is too high. The pressure can be higher
Also, the delay should be lower. I have a naked connection delay of 300+ here. I often shoot out bullets, and the blood is reduced after half a second, so it is difficult to measure the accuracy.
Energy weapons, focus and havok, preheat and turbo have a big impact, don't care here, it will be soon
total = 0 # 总计时 ms
delay = 1 # 延迟 ms
pixel = 4 # 抖动像素
while True:
if not data[fire]:
break
# 下压
if total < 30:
toolkit.Mouse.move(0, 5)
time.sleep(delay / 1000)
total += delay
else:
toolkit.Mouse.move(0, 1)
time.sleep(delay / 1000)
total += delay
# 抖枪
toolkit.Mouse.move(pixel, 0)
time.sleep(delay / 1000)
total += delay
toolkit.Mouse.move(-pixel, 0)
time.sleep(delay / 1000)
total += delay
cfg.py
mode = 'mode'
name = 'name'
game = 'game'
data = 'data'
pack = 'pack' # 背包
color = 'color'
point = 'point'
index = 'index'
shake = 'shake'
speed = 'speed'
count = 'count'
switch = 'switch'
bullet = 'bullet' # 子弹
differ = 'differ'
suppress = 'suppress'
strength = 'strength'
positive = 'positive' # 肯定的
negative = 'negative' # 否定的
# 检测数据
detect = {
"3440:1440": {
game: [ # 判断是否在游戏中
{
point: (236, 1344), # 点的坐标, 血条左上角
color: 0x00FFFFFF # 点的颜色, 255, 255, 255
},
{
point: (2692, 1372), # 生存物品右下角
color: 0x959595 # 149, 149, 149
}
],
pack: {
# 背包状态, 有无武器, 选择的武器
point: (2900, 1372), # 两把武器时, 1号武器上面边框分界线的上半部分, y+1 就是1号武器上面边框分界线的下半部分
color: 0x808080, # 无武器时, 灰色, 128, 128, 128
'0x447bb4': 1, # 轻型弹药武器, 子弹类型: 1/2/3/4/5/6/None(无武器)
'0x839b54': 2, # 重型弹药武器
'0x3da084': 3, # 能量弹药武器
'0xce5f6e': 4, # 狙击弹药武器
'0xf339b': 5, # 霰弹枪弹药武器
'0x5302ff': 6, # 空投武器
},
mode: {
# 武器模式, 全自动/半自动/单发/其他
color: 0x00FFFFFF,
'1': (3151, 1347), # 全自动
'2': (3171, 1351), # 半自动
},
name: {
# 武器名称判断
color: 0x00FFFFFF,
'1': {
# 1号武器
'1': [ # 轻型弹药武器
(2959, 1386), # 1: RE-45 自动手枪
(2970, 1385), # 2: 转换者冲锋枪
(2972, 1386), # 3: R-301 卡宾枪
(2976, 1386), # 4: R-99 冲锋枪
(2980, 1386), # 5: P2020 手枪
(2980, 1384), # 6: 喷火轻机枪
(2987, 1387), # 7: G7 侦查枪
(3015, 1386), # 8: CAR (轻型弹药)
],
'2': [ # 重型弹药武器
(2957, 1385), # 1: 赫姆洛克突击步枪
(2982, 1385), # 2: 猎兽冲锋枪
(2990, 1393), # 3: 平行步枪
(3004, 1386), # 4: 30-30
(3015, 1386), # 5: CAR (重型弹药)
],
'3': [ # 能量弹药武器
(2955, 1386), # 1: L-STAR能量机枪
(2970, 1384), # 2: 三重式狙击枪
(2981, 1385), # 3: 电能冲锋枪
(2986, 1384), # 4: 专注轻机枪
(2980, 1384), # 5: 哈沃克步枪
],
'4': [ # 狙击弹药武器
(2969, 1395), # 1: 哨兵狙击步枪
(2999, 1382), # 2: 充能步枪
(2992, 1385), # 3: 辅助手枪
(3016, 1383), # 4: 长弓
],
'5': [ # 霰弹枪弹药武器
(2957, 1384), # 1: 和平捍卫者霰弹枪
(2995, 1382), # 2: 莫桑比克
(3005, 1386), # 3: EVA-8
],
'6': [ # 空投武器
(2958, 1384), # 1: 克雷贝尔狙击枪
(2983, 1384), # 2: 敖犬霰弹枪
(3003, 1383), # 3: 波塞克
(3014, 1383), # 4: 暴走
]
},
'2': {
differ: 195 # 直接用1的坐标, 横坐标右移195就可以了
}
}
},
"2560:1440": {
},
"2560:1080": {
},
"1920:1080": {
}
}
# 武器数据
weapon = {
'1': {
# 轻型弹药武器
'1': {
name: 'RE-45 自动手枪', # 全程往右飘
shake: {
speed: 80,
count: 10,
strength: 5,
}
},
'2': {
name: '转换者冲锋枪',
shake: {
speed: 100,
count: 10,
strength: 7,
}
},
'3': {
name: 'R-301 卡宾枪',
shake: {
speed: 74, # 74ms打一发子弹
count: 6, # 压制前6发
strength: 5, # 压制的力度(下移的像素)
},
suppress: {
speed: 74,
}
},
'4': {
name: 'R-99 冲锋枪',
shake: {
speed: 55.5,
count: 13,
strength: 8,
}
},
'5': {
name: 'P2020 手枪',
},
'6': {
name: '喷火轻机枪',
shake: {
speed: 111,
count: 8,
strength: 5,
}
},
'7': {
name: 'G7 侦查枪',
},
'8': {
name: 'CAR (轻型弹药)',
shake: {
speed: 64.5,
count: 10,
strength: 7,
}
}
},
'2': {
# 重型弹药武器
'1': {
name: '赫姆洛克突击步枪',
shake: {
speed: 50,
count: 3,
strength: 6,
}
},
'2': {
name: '猎兽冲锋枪',
shake: {
speed: 50,
count: 5,
strength: 6,
}
},
'3': {
name: '平行步枪',
shake: {
speed: 100,
count: 5,
strength: 5,
}
},
'4': {
name: '30-30',
},
'5': {
name: 'CAR (重型弹药)',
shake: {
speed: 64.5,
count: 10,
strength: 7,
}
}
},
'3': {
# 能量弹药武器
'1': {
name: 'L-STAR能量机枪',
shake: {
speed: 100,
count: 10,
strength: 5,
}
},
'2': {
name: '三重式狙击枪',
},
'3': {
name: '电能冲锋枪',
shake: {
speed: 83.3,
count: 10,
strength: 7,
}
},
'4': {
name: '专注轻机枪',
shake: {
speed: 100,
count: 10,
strength: 7,
}
},
'5': {
name: '哈沃克步枪',
shake: {
speed: 100,
count: 8,
strength: 6,
}
},
},
'4': {
# 狙击弹药武器
'1': {
name: '哨兵狙击步枪',
},
'2': {
name: '充能步枪',
},
'3': {
name: '辅助手枪',
},
'4': {
name: '长弓',
},
},
'5': {
# 霰弹弹药武器
'1': {
name: '和平捍卫者霰弹枪',
},
'2': {
name: '莫桑比克',
},
'3': {
name: 'EVA-8',
},
},
'6': {
# 空投武器
'1': {
name: '克雷贝尔狙击枪',
},
'2': {
name: '敖犬霰弹枪',
},
'3': {
name: '波塞克',
},
'4': {
name: '暴走',
shake: {
speed: 200,
count: 8,
strength: 2,
}
},
}
}
toolkit.py
import time
import mss # pip install mss
import ctypes
from ctypes import CDLL
import cfg
from cfg import detect, weapon
# 全局 dll
user32 = ctypes.windll.user32
gdi32 = ctypes.windll.gdi32
try:
driver = CDLL(r'mouse.device.lgs.dll') # 在Python的string前面加上‘r’, 是为了告诉编译器这个string是个raw string(原始字符串),不要转义backslash(反斜杠) '\'
ok = driver.device_open() == 1
if not ok:
print('初始化失败, 未安装lgs/ghub驱动')
except FileNotFoundError:
print('初始化失败, 缺少文件')
class Mouse:
@staticmethod
def point():
return user32.GetCursorPos()
@staticmethod
def move(x, y, absolute=False):
if ok:
mx, my = x, y
if absolute:
ox, oy = user32.GetCursorPos()
mx = x - ox
my = y - oy
driver.moveR(mx, my, True)
@staticmethod
def moveHumanoid(x, y, absolute=False):
"""
仿真移动(还没做好)
"""
if ok:
ox, oy = user32.GetCursorPos() # 原鼠标位置
mx, my = x, y # 相对移动距离
if absolute:
mx = x - ox
my = y - oy
tx, ty = ox + mx, oy + my
print(f'({
ox},{
oy}), ({
tx},{
ty}), x:{
mx},y:{
my}')
# 以绝对位置方式移动(防止相对位置丢失精度)
adx, ady = abs(mx), abs(my)
if adx <= ady:
# 水平方向移动的距离短
for i in range(1, adx):
ix = i if mx > 0 else -i
temp = int(ady / adx * abs(ix))
iy = temp if my > 0 else -temp
Mouse.move(ox + ix, oy + iy, absolute=True)
# time.sleep(0.001)
else:
# 垂直方向移动的距离短
for i in range(1, ady):
iy = i if my > 0 else -i
temp = int(adx / ady * abs(iy))
ix = temp if mx > 0 else -temp
Mouse.move(ox + ix, oy + iy, absolute=True)
# time.sleep(0.001)
@staticmethod
def down(code):
if ok:
driver.mouse_down(code)
@staticmethod
def up(code):
if ok:
driver.mouse_up(code)
@staticmethod
def click(code):
"""
:param code: 1:左键, 2:中键, 3:右键, 4:侧下键, 5:侧上键, 6:DPI键
:return:
"""
if ok:
driver.mouse_down(code)
driver.mouse_up(code)
class Keyboard:
@staticmethod
def press(code):
if ok:
driver.key_down(code)
@staticmethod
def release(code):
if ok:
driver.key_up(code)
@staticmethod
def click(code):
"""
键盘按键函数中,传入的参数采用的是键盘按键对应的键码
:param code: 'a'-'z':A键-Z键, '0'-'9':0-9, 其他的没猜出来
:return:
"""
if ok:
driver.key_down(code)
driver.key_up(code)
class Monitor:
"""
显示器
"""
sct = mss.mss()
@staticmethod
def grab(region):
"""
region: tuple, (left, top, width, height)
pip install mss
"""
left, top, width, height = region
return Monitor.sct.grab(monitor={
'left': left, 'top': top, 'width': width, 'height': height})
@staticmethod
def pixel(x, y):
"""
效率很低且不稳定, 单点检测都要耗时1-10ms
获取颜色, COLORREF 格式, 0x00FFFFFF
结果是int,
可以通过 print(hex(color)) 查看十六进制值
可以通过 print(color == 0x00FFFFFF) 进行颜色判断
"""
hdc = user32.GetDC(None)
return gdi32.GetPixel(hdc, x, y)
class Resolution:
"""
分辨率
"""
@staticmethod
def display():
"""
显示分辨率
"""
w = user32.GetSystemMetrics(0)
h = user32.GetSystemMetrics(1)
return w, h
@staticmethod
def virtual():
"""
多屏幕组合的虚拟显示器分辨率
"""
w = user32.GetSystemMetrics(78)
h = user32.GetSystemMetrics(79)
return w, h
@staticmethod
def physical():
"""
物理分辨率
"""
hdc = user32.GetDC(None)
w = gdi32.GetDeviceCaps(hdc, 118)
h = gdi32.GetDeviceCaps(hdc, 117)
return w, h
class Game:
"""
游戏工具
"""
@staticmethod
def game():
"""
是否在游戏内(顶层窗口是游戏,且正在进行一局游戏,且游戏界面上有血条)
"""
# 先判断是否是游戏窗口
hwnd = user32.GetForegroundWindow()
length = user32.GetWindowTextLengthW(hwnd)
buffer = ctypes.create_unicode_buffer(length + 1)
user32.GetWindowTextW(hwnd, buffer, length + 1)
if 'Apex Legends' != buffer.value:
return False
# 是在游戏中, 再判断下是否有血条和生存物品包
w, h = Monitor.Resolution.display()
data = detect.get(f'{
w}:{
h}').get(cfg.game)
for item in data:
x, y = item.get(cfg.point)
if Monitor.pixel(x, y) != item.get(cfg.color):
return False
return True
@staticmethod
def index():
"""
武器索引和子弹类型索引
:return: 武器位索引, 1:1号位, 2:2号位, None:无武器, 拳头(这个暂时无法判断)
子弹类型索引, 1:轻型, 2:重型, 3:能量, 4:狙击, 5:霰弹, 6:空投, None:无武器
"""
w, h = Monitor.Resolution.display()
data = detect.get(f'{
w}:{
h}').get(cfg.pack)
x, y = data.get(cfg.point)
color = Monitor.pixel(x, y)
if data.get(cfg.color) == color:
return None, None
else:
bullet = data.get(hex(color))
return (1, bullet) if color == Monitor.pixel(x, y + 1) else (2, bullet)
@staticmethod
def weapon(index, bullet):
"""
通过武器位和子弹类型识别武器, 参考:config.detect.name
:param index: 武器位, 1:1号位, 2:2号位
:param bullet: 子弹类型, 1:轻型, 2:重型, 3:能量, 4:狙击, 5:霰弹, 6:空投
:return:
"""
w, h = Monitor.Resolution.display()
data = detect.get(f'{
w}:{
h}').get(cfg.name)
color = data.get(cfg.color)
if index == 1:
lst = data.get(str(index)).get(str(bullet))
for i in range(len(lst)):
x, y = lst[i]
if color == Monitor.pixel(x, y):
return i + 1
elif index == 2:
differ = data.get(str(index)).get(cfg.differ)
lst = data.get(str(1)).get(str(bullet))
for i in range(len(lst)):
x, y = lst[i]
if color == Monitor.pixel(x + differ, y):
return i + 1
return None
@staticmethod
def mode():
"""
武器模式
:return: 1:全自动, 2:半自动, None:其他
"""
w, h = Monitor.Resolution.display()
data = detect.get(f'{
w}:{
h}').get(cfg.mode)
color = data.get(cfg.color)
x, y = data.get('1')
if color == Monitor.pixel(x, y):
return 1
x, y = data.get('2')
if color == Monitor.pixel(x, y):
return 2
return None
@staticmethod
def detect(data):
"""
决策是否需要压枪, 向信号量写数据
"""
if data[cfg.switch] is False:
print('开关已关闭')
return
t1 = time.perf_counter_ns()
if Game.game() is False:
print('不在游戏中')
data[cfg.shake] = None
return
index, bullet = Game.index()
if (index is None) | (bullet is None):
print('没有武器')
data[cfg.shake] = None
return
if Game.mode() is None:
print('不是自动/半自动武器')
data[cfg.shake] = None
return
arms = Game.weapon(index, bullet)
if arms is None:
print('识别武器失败')
data[cfg.shake] = None
return
# 检测通过, 需要压枪
gun = weapon.get(str(bullet)).get(str(arms))
data[cfg.shake] = gun.get(cfg.shake) # 记录当前武器抖动参数
t2 = time.perf_counter_ns()
print(f'耗时:{
t2-t1}ns, 约{
(t2-t1)//1000000}ms, {
gun.get(cfg.name)}')
apex.py
import multiprocessing
import time
from multiprocessing import Process
import pynput # conda install pynput
import toolkit
end = 'end'
fire = 'fire'
shake = 'shake'
speed = 'speed'
count = 'count'
switch = 'switch'
strength = 'strength'
init = {
end: False, # 退出标记, End 键按下后改为 True, 其他进程线程在感知到变更后结束自身
switch: True, # 开关
fire: False, # 开火状态
shake: None, # 抖枪参数
}
def listener(data):
def down(x, y, button, pressed):
if data[end]:
return False # 结束监听线程
if button == pynput.mouse.Button.right:
if pressed:
toolkit.Game.detect(data)
elif button == pynput.mouse.Button.left:
data[fire] = pressed
mouse = pynput.mouse.Listener(on_click=down)
mouse.start()
def release(key):
if key == pynput.keyboard.Key.end:
# 结束程序
data[end] = True
return False
elif key == pynput.keyboard.Key.home:
# 压枪开关
data[switch] = not data[switch]
elif key == pynput.keyboard.Key.esc:
toolkit.Game.detect(data)
elif key == pynput.keyboard.Key.tab:
toolkit.Game.detect(data)
elif key == pynput.keyboard.KeyCode.from_char('1'):
toolkit.Game.detect(data)
elif key == pynput.keyboard.KeyCode.from_char('2'):
toolkit.Game.detect(data)
elif key == pynput.keyboard.KeyCode.from_char('3'):
toolkit.Game.detect(data)
elif key == pynput.keyboard.KeyCode.from_char('e'):
toolkit.Game.detect(data)
elif key == pynput.keyboard.KeyCode.from_char('v'):
toolkit.Game.detect(data)
keyboard = pynput.keyboard.Listener(on_release=release)
keyboard.start()
keyboard.join() # 卡住监听进程, 当键盘线程结束后, 监听进程才能结束
def suppress(data):
while True:
if data[end]:
break
if data[switch] is False:
continue
if data[fire] & (data[shake] is not None):
# 301 大约75ms一发子弹
total = 0 # 总计时 ms
delay = 1 # 延迟 ms
pixel = 4 # 抖动像素
while True:
if not data[fire]:
break
# 下压
t = time.perf_counter_ns()
if total < data[shake][speed] * data[shake][count]:
toolkit.Mouse.move(0, data[shake][strength])
time.sleep(delay / 1000)
total += delay
else:
toolkit.Mouse.move(0, 1)
time.sleep(delay / 1000)
total += delay
# 抖枪
toolkit.Mouse.move(pixel, 0)
time.sleep(delay / 1000)
total += delay
toolkit.Mouse.move(-pixel, 0)
time.sleep(delay / 1000)
total += delay
total += (time.perf_counter_ns() - t) // 1000 // 1000
if __name__ == '__main__':
multiprocessing.freeze_support() # windows 平台使用 multiprocessing 必须在 main 中第一行写这个
manager = multiprocessing.Manager()
data = manager.dict() # 创建进程安全的共享变量
data.update(init) # 将初始数据导入到共享变量
# 将键鼠监听和压枪放到单独进程中跑
p1 = Process(target=listener, args=(data,)) # 监听进程
p2 = Process(target=suppress, args=(data,)) # 压枪进程
p1.start()
p2.start()
p1.join() # 卡住主进程, 当进程 listener 结束后, 主进程才会结束
The third stage realizes the abandonment of the gun shaking technique and the conventional recoil offset method (in optimization)
Adjusted a few guns, with or without turbo, with or without dual triggers, with pressure gun parameters using pressure gun parameters, others using gun shaking parameters, pressure gun and gun shaking coexist
My in-game mouse settings are like this, make sure that the ADS of each scope is the same, the mouse DPI is 3200
The final effect is that 20 meters is very stable, 30 meters will be close, 50 meters is not enough, there is a chance of being knocked down in one shuttle, and then it will be meaningless.
How to adjust the gun parameters
I think the most important point in adjusting parameters is to first calculate the correct rate of fire of bullets (average time per bullet).
I summarize the test method. First of all, each bullet usually takes 50 to 150 milliseconds. Let’s assume that it is 100. To see how many bullets there are, copy as many gun pressure data as possible. For example
R-301 This gun, plus gold expansion, 28 bullets, then prepare the following initial data first, the three parameters are, the value of the mouse horizontal movement / the value of vertical movement / sleep time after moving, of course, you can also with other parameters
First, set the mouse movement value corresponding to the last bullet to 10000, to see if the mouse has a large displacement when the bullet is fired, and then adjust the latter 100 until it just matches, and then you can start to adjust the mouse parameters.
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100], #
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100], #
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100], #
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100], #
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100],
[0, 0, 100], #
[0, 0, 100],
[0, 0, 100],
[10000, 0, 100],
When adjusting the mouse parameters, you should adjust them one by one from top to bottom, because a change on the top has a great impact on the bottom, and it is likely to cause the white tone below.
For example, when adjusting the vertical suppression, the 1x mirror is aimed at this bar at 30 meters, and strive to basically be on the bar, the vertical is ok, and the horizontal is the same.
You can also use the video recording tool to record part of the central area of the screen, and then play it at 0.1 times speed, and carefully check whether the suppression strength is appropriate
The final effect is that it is not very stable, the performance of the 123x mirror is not consistent, and the deviation of the 3x mirror is the largest. Is it difficult to make a set of parameters for each mirror?
In-game test
On the whole, the performance will be fine, the parallel is ok, the others are average, it is still far from a line
existing problems
- Using the color-picking judgment method, the single-point color-picking takes 1-10ms, and the performance is insufficient
- The detection of the weapon name uses the traversal method of O(n) time complexity. In the case of the low efficiency of the color selection judgment method, the performance is not excellent and stable enough, and it is expected to achieve O(1)
- It is impossible to judge whether there is a weapon (there is a weapon but I use my fist, which may cause the pressure gun to be triggered by mistake)
- It will freeze after running for a while. It is very stable. It freezes every two seconds on average. All the mouse, keyboard, and computer screens freeze at the same time, which seriously affects the game. I don't know why.
- It is not possible to simulate the effect of clicking the left button when pressing the left button, so it is temporarily impossible to realize the function of changing a single-shot gun to a burst gun.
Detailed code
cfg.py
mode = 'mode'
name = 'name'
game = 'game'
data = 'data'
pack = 'pack'
color = 'color'
point = 'point'
index = 'index'
shake = 'shake'
speed = 'speed'
count = 'count'
armed = 'armed'
empty = 'empty'
switch = 'switch'
bullet = 'bullet' # 子弹
differ = 'differ'
turbo = 'turbo'
trigger = 'trigger'
restrain = 'restrain'
strength = 'strength'
positive = 'positive' # 肯定的
negative = 'negative' # 否定的
# 检测数据
detect = {
"3440:1440": {
game: [ # 判断是否在游戏中
{
point: (236, 1344), # 点的坐标, 血条左上角
color: 0x00FFFFFF # 点的颜色, 255, 255, 255
},
{
point: (2692, 1372), # 生存物品右下角
color: 0x959595 # 149, 149, 149
}
],
pack: {
# 背包状态, 有无武器, 选择的武器
point: (2900, 1372), # 两把武器时, 1号武器上面边框分界线的上半部分, y+1 就是1号武器上面边框分界线的下半部分
color: 0x808080, # 无武器时, 灰色, 128, 128, 128
'0x447bb4': 1, # 轻型弹药武器, 子弹类型: 1/2/3/4/5/6/None(无武器)
'0x839b54': 2, # 重型弹药武器
'0x3da084': 3, # 能量弹药武器
'0xce5f6e': 4, # 狙击弹药武器
'0xf339b': 5, # 霰弹枪弹药武器
'0x5302ff': 6, # 空投武器
},
mode: {
# 武器模式, 全自动/半自动/单发/其他
color: 0x00FFFFFF,
'1': (3151, 1347), # 全自动
'2': (3171, 1351), # 半自动
},
armed: {
# 是否持有武器(比如有武器但用拳头就是未持有武器)
},
empty: {
# 是否空弹夹(武器里子弹数为0)
color: 0x00FFFFFF,
'1': (3204, 1306), # 十位数, 该点白色即非0, 非0则一定不空
'2': (3229, 1294), # 个位数, 该点白色即为0, 十位为0且个位为0为空
},
name: {
# 武器名称判断
color: 0x00FFFFFF,
'1': {
# 1号武器
'1': [ # 轻型弹药武器
(2959, 1386), # 1: RE-45 自动手枪
(2970, 1385), # 2: 转换者冲锋枪
(2972, 1386), # 3: R-301 卡宾枪
(2976, 1386), # 4: R-99 冲锋枪
(2980, 1386), # 5: P2020 手枪
(2980, 1384), # 6: 喷火轻机枪
(2987, 1387), # 7: G7 侦查枪
(3015, 1386), # 8: CAR (轻型弹药)
],
'2': [ # 重型弹药武器
(2957, 1385), # 1: 赫姆洛克突击步枪
(2982, 1385), # 2: 猎兽冲锋枪
(2990, 1393), # 3: 平行步枪
(3004, 1386), # 4: 30-30
(3015, 1386), # 5: CAR (重型弹药)
],
'3': [ # 能量弹药武器
(2955, 1386), # 1: L-STAR 能量机枪
(2970, 1384), # 2: 三重式狙击枪
(2981, 1385), # 3: 电能冲锋枪
(2986, 1384), # 4: 专注轻机枪
(2980, 1384), # 5: 哈沃克步枪
],
'4': [ # 狙击弹药武器
(2969, 1395), # 1: 哨兵狙击步枪
(2999, 1382), # 2: 充能步枪
(2992, 1385), # 3: 辅助手枪
(3016, 1383), # 4: 长弓
],
'5': [ # 霰弹枪弹药武器
(2957, 1384), # 1: 和平捍卫者霰弹枪
(2995, 1382), # 2: 莫桑比克
(3005, 1386), # 3: EVA-8
],
'6': [ # 空投武器
(2958, 1384), # 1: 克雷贝尔狙击枪
(2959, 1384), # 2: 手感卓越的刀刃
(2983, 1384), # 3: 敖犬霰弹枪
(3003, 1383), # 4: 波塞克
(3014, 1383), # 5: 暴走
]
},
'2': {
differ: 195 # 直接用1的坐标, 横坐标右移195就可以了
}
},
turbo: {
# 涡轮
color: 0x00FFFFFF,
'3': {
differ: 2, # 有涡轮和没涡轮的索引偏移
'4': (3072, 1358), # 专注轻机枪 涡轮检测位置
'5': (3034, 1358), # 哈沃克步枪 涡轮检测位置
}
},
trigger: {
# 双发扳机
color: 0x00FFFFFF,
'1': {
differ: 2,
'7': (3072, 1358), # G7 侦查枪 双发扳机检测位置
},
'5': {
differ: 1,
'3': (3034, 1358), # EVA-8 双发扳机检测位置
}
}
},
"2560:1440": {
},
"2560:1080": {
},
"1920:1080": {
}
}
# 武器数据
weapon = {
'1': {
# 轻型弹药武器
'1': {
name: 'RE-45 自动手枪', # 全程往右飘
shake: {
speed: 80,
count: 10,
strength: 5,
},
restrain: [
[1, -2, 10, 80], #
[1, -2, 10, 80],
[1, -2, 10, 80],
[1, -4, 10, 80],
[1, -6, 10, 80],
[1, -7, 8, 80], #
[1, -7, 8, 80],
[1, -7, 8, 80],
[1, -7, 8, 80],
[1, -7, 8, 80],
[1, -1, 5, 80], #
[1, -1, 5, 80],
[1, -1, 5, 80],
[1, -1, 5, 80],
[1, -1, 5, 80],
[1, -1, 5, 80], #
[1, -1, 3, 80],
[1, -1, 3, 80],
[1, -1, 3, 80],
[1, -1, 3, 80],
[1, -1, 3, 80], #
[1, -2, 3, 80],
[1, -2, 3, 80],
[1, -2, 3, 80],
[1, -2, 3, 80],
[1, -5, 3, 80], #
[1, -5, 3, 80],
[1, -10, 3, 80],
[1, -10, 3, 80],
[1, -10, 3, 80],
]
},
'2': {
name: '转换者冲锋枪',
shake: {
speed: 100,
count: 10,
strength: 7,
},
restrain: [
[1, 0, 15, 94],
[1, 0, 15, 94],
[1, 0, 15, 94],
[1, 0, 15, 94],
[1, 0, 15, 94], #
[1, 0, 15, 94],
[1, 0, 15, 94],
[1, 0, 10, 94],
[1, 0, 10, 94],
[1, 0, 10, 94], #
[1, -5, 5, 94],
[1, -5, 5, 94],
[1, -5, 5, 94],
[1, 0, 5, 94],
[1, 0, 5, 94], #
[1, 0, 5, 94],
[1, 5, 5, 94],
[1, 5, 5, 94],
[1, 5, 5, 94],
[1, 0, 5, 94], #
[1, 0, 5, 94],
[1, 0, 5, 94],
[1, 0, 5, 94],
[1, 0, 5, 94],
[1, 0, 5, 94], #
[1, 0, 5, 94],
[1, 0, 0, 94],
]
},
'3': {
name: 'R-301 卡宾枪',
shake: {
speed: 64, # 74ms打一发子弹
count: 6, # 压制前6发
strength: 5, # 压制的力度(下移的像素)
},
restrain: [
[1, -5, 10, 70],
[1, 0, 10, 70],
[1, -5, 10, 70],
[1, -2, 10, 70],
[1, 0, 10, 70], #
[1, 0, 5, 70],
[1, 0, 0, 70],
[1, -5, 0, 70],
[1, -5, 5, 70],
[1, 0, 0, 70], #
[1, 0, 0, 70],
[1, 5, 10, 70],
[1, 5, 5, 70],
[1, 5, 0, 70],
[1, 5, 0, 70], #
[1, 0, 0, 70],
[1, 5, 0, 70],
[1, 5, 10, 70],
[1, 0, 10, 70],
[1, -5, 0, 70], #
[1, -5, 0, 70],
[1, -5, 0, 70],
[1, -5, 0, 70],
[1, -5, 0, 70],
[1, 0, 0, 70], #
[1, 0, 0, 70],
[1, 0, 0, 70],
[1, 0, 0, 64],
]
},
'4': {
name: 'R-99 冲锋枪',
shake: {
speed: 55.5,
count: 13,
strength: 8,
},
restrain: [
[1, 0, 10, 48],
[1, 0, 10, 48],
[1, 0, 10, 48],
[1, -5, 10, 48],
[1, -5, 10, 48], #
[1, -5, 10, 48],
[1, -5, 10, 48],
[1, 0, 10, 48],
[1, 0, 10, 48],
[1, 0, 10, 48], #
[1, 5, 10, 48],
[1, 5, 10, 48],
[1, 5, 10, 48],
[1, 0, 10, 48],
[1, 0, 0, 48], #
[1, -5, 0, 48],
[1, -10, 0, 48],
[1, 0, 0, 48],
[1, 0, 0, 48],
[1, 5, 5, 48], #
[1, 10, 5, 48],
[1, 10, 5, 48],
[1, 5, 0, 48],
[1, 0, 0, 48],
[1, -5, 0, 48], #
[1, -5, 0, 48],
[1, -5, 0, 48],
]
},
'5': {
name: 'P2020 手枪',
restrain: [
[2, 1, 100],
]
},
'6': {
name: '喷火轻机枪',
shake: {
speed: 110,
count: 8,
strength: 5,
},
restrain: [
[1, 0, 20, 100],
[1, 5, 15, 100],
[1, 5, 15, 100],
[1, 5, 15, 100],
[1, 5, 15, 100], #
[1, 5, 15, 100],
[1, -5, 10, 100],
[1, -5, 0, 100],
[1, -5, 0, 100],
[1, -5, 0, 100], #
[1, 0, 0, 100],
[1, 0, 5, 100],
[1, 0, 5, 100],
[1, 5, 5, 100],
[1, 10, 5, 100], #
[1, 10, 5, 100],
[1, 5, 5, 100],
[1, 0, 5, 100],
[1, 0, 5, 100],
[1, 0, 5, 100], # 20
[1, 0, 0, 100],
[1, 0, 0, 100],
[1, 0, 0, 100],
[1, 0, 0, 100],
[1, -5, 5, 100], #
[1, -5, 5, 100],
[1, -5, 5, 100],
[1, -5, 5, 100],
[1, 0, 5, 100],
[1, 0, 5, 100], #
[1, 0, 5, 100],
[1, 0, 5, 100],
[1, 0, 5, 100],
[1, 0, 5, 100],
[1, 0, 5, 100], #
[1, 0, 5, 100],
[1, 0, 5, 100],
[1, 0, 5, 100],
[1, 0, 5, 100],
[1, 0, 5, 100], #
[1, 0, 5, 100],
[1, 0, 5, 100],
[1, 0, 5, 100],
[1, 0, 5, 100],
[1, 0, 5, 100], #
[1, 0, 5, 100],
[1, 0, 5, 100],
[1, 0, 5, 100],
[1, 0, 5, 100],
[1, 0, 0, 100], #
]
},
'7': {
name: 'G7 侦查枪',
},
'8': {
name: 'CAR (轻型弹药)',
shake: {
speed: 64.5,
count: 10,
strength: 7,
},
restrain: [
[1, 0, 10, 58], #
[1, 3, 10, 58],
[1, 3, 10, 58],
[1, 3, 10, 58],
[1, 3, 10, 58],
[1, 3, 10, 58], #
[1, 3, 10, 58],
[1, 3, 10, 58],
[1, -5, 10, 58],
[1, -5, 10, 58],
[1, -5, 5, 58], #
[1, -5, 10, 58],
[1, -5, 0, 58],
[1, 0, 0, 58],
[1, 5, 0, 58],
[1, 5, 3, 58], #
[1, 5, 3, 58],
[1, -5, 3, 58],
[1, -5, 3, 58],
[1, -5, 3, 58],
[1, 0, 0, 58], #
[1, 0, 0, 58],
[1, 0, 0, 58],
[1, 0, 3, 58],
[1, 0, 3, 58],
[1, 0, 3, 58], #
[1, 0, 3, 58],
]
},
'9': {
name: 'G7 侦查枪 (双发扳机)',
restrain: [
[1, 0, 5, 20]
]
},
},
'2': {
# 重型弹药武器
'1': {
name: '赫姆洛克突击步枪',
shake: {
speed: 50,
count: 3,
strength: 6,
}
},
'2': {
name: '猎兽冲锋枪',
shake: {
speed: 50,
count: 5,
strength: 6,
}
},
'3': {
name: '平行步枪',
shake: {
speed: 100,
count: 5,
strength: 5,
},
restrain: [
[1, 0, 10, 100], #
[1, 5, 10, 100],
[1, 5, 10, 100],
[1, 5, 10, 100],
[1, 5, 10, 100],
[1, -5, 10, 100], #
[1, -5, 0, 100],
[1, -5, 0, 100],
[1, -5, 0, 100],
[1, 0, 5, 100],
[1, 5, 5, 100], #
[1, 5, 5, 100],
[1, 5, 0, 100],
[1, 5, 0, 100],
[1, 0, 0, 100],
[1, 5, 5, 100], #
[1, 5, 5, 100],
[1, 5, 5, 100],
[1, 0, 0, 100],
[1, 0, 0, 100],
[1, -5, 5, 100], #
[1, -5, 5, 100],
[1, -5, 5, 100],
[1, -0, 5, 100],
[1, 5, 5, 100],
[1, 5, 5, 100], #
[1, 5, 5, 100],
[1, -5, -5, 100],
[1, -5, 5, 100],
[1, -5, 5, 100],
]
},
'4': {
name: '30-30',
},
'5': {
name: 'CAR (重型弹药)',
shake: {
speed: 58,
count: 10,
strength: 7,
},
restrain: [
[1, 0, 10, 58], #
[1, 3, 10, 58],
[1, 3, 10, 58],
[1, 3, 10, 58],
[1, 3, 10, 58],
[1, 3, 10, 58], #
[1, 3, 10, 58],
[1, 3, 10, 58],
[1, -5, 10, 58],
[1, -5, 10, 58],
[1, -5, 5, 58], #
[1, -5, 10, 58],
[1, -5, 0, 58],
[1, 0, 0, 58],
[1, 5, 0, 58],
[1, 5, 3, 58], #
[1, 5, 3, 58],
[1, -5, 3, 58],
[1, -5, 3, 58],
[1, -5, 3, 58],
[1, 0, 0, 58], #
[1, 0, 0, 58],
[1, 0, 0, 58],
[1, 0, 3, 58],
[1, 0, 3, 58],
[1, 0, 3, 58], #
[1, 0, 3, 58],
]
}
},
'3': {
# 能量弹药武器
'1': {
name: 'L-STAR 能量机枪',
shake: {
speed: 100,
count: 10,
strength: 5,
}
},
'2': {
name: '三重式狙击枪',
},
'3': {
name: '电能冲锋枪',
shake: {
speed: 83.3,
count: 10,
strength: 7,
},
restrain: [
[1, -5, 15, 80],
[1, 0, 15, 80],
[1, 0, 15, 80],
[1, 0, 15, 80],
[1, 0, 15, 80], #
[1, -5, 10, 80],
[1, -5, 10, 80],
[1, -5, 10, 80],
[1, 0, 10, 80],
[1, 5, 10, 80], #
[1, 5, 5, 80],
[1, 5, 5, 80],
[1, 5, 5, 80],
[1, 0, 5, 80],
[1, 0, 5, 80], #
[1, 0, 5, 80],
[1, 0, 0, 80],
[1, 0, 0, 80],
[1, 0, 0, 80],
[1, 0, 0, 80], #
[1, 0, 0, 80],
[1, 5, 0, 80],
[1, 5, 0, 80],
[1, 5, 0, 80],
[1, 0, 0, 80], #
[1, 0, 0, 80],
]
},
'4': {
name: '专注轻机枪',
shake: {
speed: 100,
count: 10,
strength: 7,
}
},
'5': {
name: '哈沃克步枪',
shake: {
speed: 100,
count: 8,
strength: 6,
},
restrain: [
[1, 0, 0, 400], # 延迟
[1, -5, 10, 88], # 1
[1, -5, 15, 88],
[1, 0, 15, 88],
[1, 0, 15, 88],
[1, 0, 15, 88],
[1, 5, 10, 88], #
[1, 5, 10, 88],
[1, 5, 10, 88],
[1, 5, 10, 88],
[1, -5, 5, 88],
[1, -5, 0, 88], # 1
[1, -5, 0, 88],
[1, -10, 0, 88],
[1, -10, 0, 88],
[1, -5, 0, 88],
[1, 0, 5, 88], #
[1, 10, 5, 88],
[1, 10, 5, 88],
[1, 0, 0, 88],
[1, 0, 0, 88],
[1, 5, 10, 88], # 1
[1, 5, 10, 88],
[1, 0, 10, 88],
[1, 5, 10, 88],
[1, 5, 10, 88],
[1, 5, 10, 88], #
[1, 5, 5, 88],
[1, 5, 5, 88],
[1, 0, 5, 88],
[1, 0, 0, 88],
[1, 0, 0, 88], # 1
[1, 0, 0, 88],
[1, 0, 5, 88],
[1, 0, 5, 88],
[1, 0, 5, 88],
[1, 0, 5, 88], #
]
},
'6': {
name: '专注轻机枪 (涡轮)',
shake: {
speed: 100,
count: 10,
strength: 7,
}
},
'7': {
name: '哈沃克步枪 (涡轮)',
shake: {
speed: 100,
count: 8,
strength: 6,
},
restrain: [
[1, -5, 10, 88], # 1
[1, -5, 15, 88],
[1, 0, 15, 88],
[1, 0, 15, 88],
[1, 0, 15, 88],
[1, 5, 10, 88], #
[1, 5, 10, 88],
[1, 5, 10, 88],
[1, 5, 10, 88],
[1, -5, 5, 88],
[1, -5, 0, 88], # 1
[1, -5, 0, 88],
[1, -10, 0, 88],
[1, -10, 0, 88],
[1, -5, 0, 88],
[1, 0, 5, 88], #
[1, 10, 5, 88],
[1, 10, 5, 88],
[1, 0, 0, 88],
[1, 0, 0, 88],
[1, 5, 10, 88], # 1
[1, 5, 10, 88],
[1, 0, 10, 88],
[1, 5, 10, 88],
[1, 5, 10, 88],
[1, 5, 10, 88], #
[1, 5, 5, 88],
[1, 5, 5, 88],
[1, 0, 5, 88],
[1, 0, 0, 88],
[1, 0, 0, 88], # 1
[1, 0, 0, 88],
[1, 0, 5, 88],
[1, 0, 5, 88],
[1, 0, 5, 88],
[1, 0, 5, 88], #
]
},
},
'4': {
# 狙击弹药武器
'1': {
name: '哨兵狙击步枪',
},
'2': {
name: '充能步枪',
},
'3': {
name: '辅助手枪',
},
'4': {
name: '长弓',
},
},
'5': {
# 霰弹弹药武器
'1': {
name: '和平捍卫者霰弹枪',
},
'2': {
name: '莫桑比克',
},
'3': {
name: 'EVA-8',
},
'4': {
name: 'EVA-8 (双发扳机)',
}
},
'6': {
# 空投武器
'1': {
name: '克雷贝尔狙击枪',
},
'2': {
name: '手感卓越的刀刃',
},
'3': {
name: '敖犬霰弹枪',
},
'4': {
name: '波塞克',
},
'5': {
name: '暴走',
shake: {
speed: 200,
count: 8,
strength: 2,
}
},
}
}
toolkit.py
import time
import mss # pip install mss
import ctypes
from ctypes import CDLL
import cfg
from cfg import detect, weapon
# 全局 dll
user32 = ctypes.windll.user32
gdi32 = ctypes.windll.gdi32
try:
driver = CDLL(r'mouse.device.lgs.dll') # 在Python的string前面加上‘r’, 是为了告诉编译器这个string是个raw string(原始字符串),不要转义backslash(反斜杠) '\'
ok = driver.device_open() == 1
if not ok:
print('初始化失败, 未安装lgs/ghub驱动')
except FileNotFoundError:
print('初始化失败, 缺少文件')
class Mouse:
@staticmethod
def point():
return user32.GetCursorPos()
@staticmethod
def move(x, y, absolute=False):
if ok:
if (x == 0) & (y == 0):
return
mx, my = x, y
if absolute:
ox, oy = user32.GetCursorPos()
mx = x - ox
my = y - oy
driver.moveR(mx, my, True)
@staticmethod
def moveHumanoid(x, y, absolute=False):
"""
仿真移动(还没做好)
"""
if ok:
ox, oy = user32.GetCursorPos() # 原鼠标位置
mx, my = x, y # 相对移动距离
if absolute:
mx = x - ox
my = y - oy
tx, ty = ox + mx, oy + my
print(f'({
ox},{
oy}), ({
tx},{
ty}), x:{
mx},y:{
my}')
# 以绝对位置方式移动(防止相对位置丢失精度)
adx, ady = abs(mx), abs(my)
if adx <= ady:
# 水平方向移动的距离短
for i in range(1, adx):
ix = i if mx > 0 else -i
temp = int(ady / adx * abs(ix))
iy = temp if my > 0 else -temp
Mouse.move(ox + ix, oy + iy, absolute=True)
# time.sleep(0.001)
else:
# 垂直方向移动的距离短
for i in range(1, ady):
iy = i if my > 0 else -i
temp = int(adx / ady * abs(iy))
ix = temp if mx > 0 else -temp
Mouse.move(ox + ix, oy + iy, absolute=True)
# time.sleep(0.001)
@staticmethod
def down(code):
if ok:
driver.mouse_down(code)
@staticmethod
def up(code):
if ok:
driver.mouse_up(code)
@staticmethod
def click(code):
"""
:param code: 1:左键, 2:中键, 3:右键, 4:侧下键, 5:侧上键, 6:DPI键
:return:
"""
if ok:
driver.mouse_down(code)
driver.mouse_up(code)
class Keyboard:
@staticmethod
def press(code):
if ok:
driver.key_down(code)
@staticmethod
def release(code):
if ok:
driver.key_up(code)
@staticmethod
def click(code):
"""
键盘按键函数中,传入的参数采用的是键盘按键对应的键码
:param code: 'a'-'z':A键-Z键, '0'-'9':0-9, 其他的没猜出来
:return:
"""
if ok:
driver.key_down(code)
driver.key_up(code)
class Monitor:
"""
显示器
"""
sct = mss.mss()
@staticmethod
def grab(region):
"""
region: tuple, (left, top, width, height)
pip install mss
"""
left, top, width, height = region
return Monitor.sct.grab(monitor={
'left': left, 'top': top, 'width': width, 'height': height})
@staticmethod
def pixel(x, y):
"""
效率很低且不稳定, 单点检测都要耗时1-10ms
获取颜色, COLORREF 格式, 0x00FFFFFF
结果是int,
可以通过 print(hex(color)) 查看十六进制值
可以通过 print(color == 0x00FFFFFF) 进行颜色判断
"""
hdc = user32.GetDC(None)
return gdi32.GetPixel(hdc, x, y)
class Resolution:
"""
分辨率
"""
@staticmethod
def display():
"""
显示分辨率
"""
w = user32.GetSystemMetrics(0)
h = user32.GetSystemMetrics(1)
return w, h
@staticmethod
def virtual():
"""
多屏幕组合的虚拟显示器分辨率
"""
w = user32.GetSystemMetrics(78)
h = user32.GetSystemMetrics(79)
return w, h
@staticmethod
def physical():
"""
物理分辨率
"""
hdc = user32.GetDC(None)
w = gdi32.GetDeviceCaps(hdc, 118)
h = gdi32.GetDeviceCaps(hdc, 117)
return w, h
class Game:
"""
游戏工具
"""
@staticmethod
def key():
w, h = Monitor.Resolution.display()
return f'{
w}:{
h}'
@staticmethod
def game():
"""
是否游戏窗体在最前
"""
# 先判断是否是游戏窗口
hwnd = user32.GetForegroundWindow()
length = user32.GetWindowTextLengthW(hwnd)
buffer = ctypes.create_unicode_buffer(length + 1)
user32.GetWindowTextW(hwnd, buffer, length + 1)
if 'Apex Legends' != buffer.value:
return False
return True
@staticmethod
def play():
"""
是否正在玩
"""
# 是在游戏中, 再判断下是否有血条和生存物品包
data = detect.get(Game.key()).get(cfg.game)
for item in data:
x, y = item.get(cfg.point)
if Monitor.pixel(x, y) != item.get(cfg.color):
return False
return True
@staticmethod
def index():
"""
武器索引和子弹类型索引
:return: 武器位索引, 1:1号位, 2:2号位, None:无武器
子弹类型索引, 1:轻型, 2:重型, 3:能量, 4:狙击, 5:霰弹, 6:空投, None:无武器
"""
data = detect.get(Game.key()).get(cfg.pack)
x, y = data.get(cfg.point)
color = Monitor.pixel(x, y)
if data.get(cfg.color) == color:
return None, None
else:
bi = data.get(hex(color))
return (1, bi) if color == Monitor.pixel(x, y + 1) else (2, bi)
@staticmethod
def weapon(pi, bi):
"""
通过武器位和子弹类型识别武器, 参考:config.detect.name
:param pi: 武器位, 1:1号位, 2:2号位
:param bi: 子弹类型, 1:轻型, 2:重型, 3:能量, 4:狙击, 5:霰弹, 6:空投
:return:
"""
data = detect.get(Game.key()).get(cfg.name)
color = data.get(cfg.color)
if pi == 1:
lst = data.get(str(pi)).get(str(bi))
for i in range(len(lst)):
x, y = lst[i]
if color == Monitor.pixel(x, y):
return i + 1
elif pi == 2:
differ = data.get(str(pi)).get(cfg.differ)
lst = data.get(str(1)).get(str(bi))
for i in range(len(lst)):
x, y = lst[i]
if color == Monitor.pixel(x + differ, y):
return i + 1
return None
@staticmethod
def mode():
"""
武器模式
:return: 1:全自动, 2:半自动, None:其他
"""
data = detect.get(Game.key()).get(cfg.mode)
color = data.get(cfg.color)
x, y = data.get('1')
if color == Monitor.pixel(x, y):
return 1
x, y = data.get('2')
if color == Monitor.pixel(x, y):
return 2
return None
@staticmethod
def armed():
"""
是否持有武器
"""
return True
@staticmethod
def empty():
"""
是否空弹夹
"""
data = detect.get(Game.key()).get(cfg.empty)
color = data.get(cfg.color)
x, y = data.get('1')
if color == Monitor.pixel(x, y):
return False
x, y = data.get('2')
return color == Monitor.pixel(x, y)
@staticmethod
def turbo(bi, wi):
"""
判断是否有涡轮, 只有配置了检测涡轮的武器才会做取色判断
:return: (False, None), (True, differ), 有涡轮的话, 额外返回涡轮索引偏移
"""
data = detect.get(Game.key()).get(cfg.turbo)
color = data.get(cfg.color)
data = data.get(str(bi))
if data is None:
return False, None
differ = data.get(cfg.differ)
data = data.get(str(wi))
if data is None:
return False, None
x, y = data
result = color == Monitor.pixel(x, y)
return (True, differ) if result else (False, None)
@staticmethod
def trigger(bi, wi):
"""
判断是否有双发扳机, 只有配置了检测双发扳机的武器才会做取色判断
:return: (False, None), (True, differ), 有双发扳机的话, 额外返回双发扳机索引偏移
"""
data = detect.get(Game.key()).get(cfg.trigger)
color = data.get(cfg.color)
data = data.get(str(bi))
if data is None:
return False, None
differ = data.get(cfg.differ)
data = data.get(str(wi))
if data is None:
return False, None
x, y = data
result = color == Monitor.pixel(x, y)
return (True, differ) if result else (False, None)
@staticmethod
def detect(data):
"""
决策是否需要压枪, 向信号量写数据
"""
t1 = time.perf_counter_ns()
if data.get(cfg.switch) is False:
t2 = time.perf_counter_ns()
print(f'耗时: {
t2 - t1}ns, 约{
(t2 - t1) // 1000000}ms, 开关已关闭')
return
if Game.game() is False:
data[cfg.shake] = None
data[cfg.restrain] = None
t2 = time.perf_counter_ns()
print(f'耗时: {
t2 - t1}ns, 约{
(t2 - t1) // 1000000}ms, 不在游戏中')
return
if Game.play() is False:
data[cfg.shake] = None
data[cfg.restrain] = None
t2 = time.perf_counter_ns()
print(f'耗时: {
t2 - t1}ns, 约{
(t2 - t1) // 1000000}ms, 不在游戏中')
return
pi, bi = Game.index()
if (pi is None) | (bi is None):
data[cfg.shake] = None
data[cfg.restrain] = None
t2 = time.perf_counter_ns()
print(f'耗时: {
t2 - t1}ns, 约{
(t2 - t1) // 1000000}ms, 没有武器')
return
# if Game.mode() is None:
# data[cfg.shake] = None
# data[cfg.restrain] = None
# t2 = time.perf_counter_ns()
# print(f'耗时: {t2 - t1}ns, 约{(t2 - t1) // 1000000}ms, 不是自动/半自动武器')
# return
wi = Game.weapon(pi, bi)
if wi is None:
data[cfg.shake] = None
data[cfg.restrain] = None
t2 = time.perf_counter_ns()
print(f'耗时: {
t2 - t1}ns, 约{
(t2 - t1) // 1000000}ms, 识别武器失败')
return
# 检测通过, 需要压枪
# 检测涡轮
result, differ = Game.turbo(bi, wi)
if result is False:
# 检测双发扳机
result, differ = Game.trigger(bi, wi)
# 拿对应参数
gun = weapon.get(str(bi)).get(str((wi + differ) if result else wi))
data[cfg.shake] = gun.get(cfg.shake) # 记录当前武器抖动参数
data[cfg.restrain] = gun.get(cfg.restrain) # 记录当前武器压制参数
t2 = time.perf_counter_ns()
print(f'耗时: {
t2-t1}ns, 约{
(t2-t1)//1000000}ms, {
gun.get(cfg.name)}')
apex.py
import multiprocessing
import time
from multiprocessing import Process
import pynput # conda install pynput
from toolkit import Mouse, Game
end = 'end'
fire = 'fire'
shake = 'shake'
speed = 'speed'
count = 'count'
switch = 'switch'
restart = 'restart'
restrain = 'restrain'
strength = 'strength'
init = {
end: False, # 退出标记, End 键按下后改为 True, 其他进程线程在感知到变更后结束自身
switch: True, # 检测和压枪开关
fire: False, # 开火状态
shake: None, # 抖枪参数
restrain: None, # 压枪参数
}
def listener(data):
def down(x, y, button, pressed):
if data.get(end):
return False # 结束监听线程
if button == pynput.mouse.Button.right:
if pressed:
Game.detect(data)
elif button == pynput.mouse.Button.left:
data[fire] = pressed
mouse = pynput.mouse.Listener(on_click=down)
mouse.start()
def release(key):
if key == pynput.keyboard.Key.end:
# 结束程序
data[end] = True
return False
elif key == pynput.keyboard.Key.home:
# 压枪开关
data[switch] = not data.get(switch)
elif key == pynput.keyboard.Key.esc:
Game.detect(data)
elif key == pynput.keyboard.Key.tab:
Game.detect(data)
elif key == pynput.keyboard.Key.alt_l:
Game.detect(data)
elif key == pynput.keyboard.KeyCode.from_char('1'):
Game.detect(data)
elif key == pynput.keyboard.KeyCode.from_char('2'):
Game.detect(data)
elif key == pynput.keyboard.KeyCode.from_char('3'):
Game.detect(data)
elif key == pynput.keyboard.KeyCode.from_char('e'):
Game.detect(data)
elif key == pynput.keyboard.KeyCode.from_char('r'):
Game.detect(data)
elif key == pynput.keyboard.KeyCode.from_char('v'):
Game.detect(data)
keyboard = pynput.keyboard.Listener(on_release=release)
keyboard.start()
keyboard.join() # 卡住监听进程, 当键盘线程结束后, 监听进程才能结束
def suppress(data):
while True:
if data.get(end):
break
if data.get(switch) is False:
continue
if data.get(fire):
if data.get(restrain) is not None:
for item in data.get(restrain):
if not data.get(fire): # 停止开火
break
t1 = time.perf_counter_ns()
if not Game.game(): # 不在游戏中
break
if not Game.armed(): # 未持有武器
break
if Game.empty(): # 弹夹为空
break
t2 = time.perf_counter_ns()
# operation: # 1:移动 2:按下
operation = item[0]
if operation == 1:
temp, x, y, delay = item
Mouse.move(x, y)
time.sleep((delay - (t2 - t1) // 1000 // 1000) / 1000)
elif operation == 2:
temp, code, delay = item
Mouse.click(code)
time.sleep((delay - (t2 - t1) // 1000 // 1000) / 1000)
elif data.get(shake) is not None:
total = 0 # 总计时 ms
delay = 1 # 延迟 ms
pixel = 4 # 抖动像素
while True:
if not data[fire]: # 停止开火
break
if not Game.game(): # 不在游戏中
break
if not Game.armed(): # 未持有武器
break
if Game.empty(): # 弹夹为空
break
t = time.perf_counter_ns()
if total < data[shake][speed] * data[shake][count]:
Mouse.move(0, data[shake][strength])
time.sleep(delay / 1000)
total += delay
else:
Mouse.move(0, 1)
time.sleep(delay / 1000)
total += delay
# 抖枪
Mouse.move(pixel, 0)
time.sleep(delay / 1000)
total += delay
Mouse.move(-pixel, 0)
time.sleep(delay / 1000)
total += delay
total += (time.perf_counter_ns() - t) // 1000 // 1000
if __name__ == '__main__':
multiprocessing.freeze_support() # windows 平台使用 multiprocessing 必须在 main 中第一行写这个
manager = multiprocessing.Manager()
data = manager.dict() # 创建进程安全的共享变量
data.update(init) # 将初始数据导入到共享变量
# 将键鼠监听和压枪放到单独进程中跑
p1 = Process(daemon=True, target=listener, args=(data,)) # 监听进程
p2 = Process(target=suppress, args=(data,)) # 压枪进程
p1.start()
p2.start()
p1.join() # 卡住主进程, 当进程 listener 结束后, 主进程才会结束