Python加CH9329模块实现云顶之弈自动刷局数

您所在的位置:网站首页 云顶之奕有脚本么 Python加CH9329模块实现云顶之弈自动刷局数

Python加CH9329模块实现云顶之弈自动刷局数

2024-07-10 12:35| 来源: 网络整理| 查看: 265

前言

2.0版本脚本,不需要硬件模块。

现在的LOL活动是又氪又肝,各种代币宝典之类的太费肝了。所以我就想搞个云顶自动刷局数的脚本。

流程大概如下:

一开始我打算直接用pyautogui这个模块控制鼠标和键盘实现这个功能:

但是LOL在这方面好像有防备,在客户端上面可以控制鼠标移动,但是控制鼠标点击的时候,它没反应。而且到了游戏内时候,直接无法用pyautogui模块控制鼠标移动,也无法控制鼠标点击。这也就是为什么要用CH9329这个硬件模块了(某宝加邮费总共23块不到,外加一个CH340 USB转TTL模块,如果自己没有,买一个大概7块钱,买的时候会送杜邦线)。我觉得LOL总不能检测到我用了个假鼠标和键盘吧。

虽然,pyautogui的一部分功能不能使用了,但是它还有一些比较有用的功能,比如在屏幕上定位一幅图片的位置(pyautogui.locateOnScreen),比如我们可以将上面流程图里面的按钮都截图,然后使用pyautogui去定位这些按钮的位置,然后通过CH9329模块进行硬件鼠标左键单击。pyautogui还可以通过界面的标题来获取到windows下面的hwnd信息(pyautogui.getWindowsWithTitle),这样我们就可以用来检测游戏是否启动或者结束了。

CH9329模块是通过串口接收指令,然后自己上报鼠标或者键盘事件。它支持鼠标的相对移动,绝对移动,键盘按键。

以下是这个脚本所需要的Python库环境:

Python 3.7

pyautogui,PIL,serial,opencv-python

注意:虽然没有直接调用opencv的功能,但是pyautogui模块在模糊定位图片时候,需要用到opencv库。

控制CH9239模块

CH9239芯片通信协议的资料:

链接:某度网盘 提取码:rarl

首先根据CH9239的通信协议,实现控制鼠标移动点击函数,实现键盘按键功能,把pyautogui模块不可用的功能用我们的硬件模块代替。

鼠标绝对移动并单击按键(模仿pyautogui模块的API写的,后来发现用不到多次点击的功能):

mserial = serial.Serial("COM3", 115200, timeout=1) def hard_click(x, y, clicks=1, interval=0.0, button=pg.LEFT, duration=0.0): global mserial nx = int(x * 4096 / 1920) ny = int(y * 4096 / 1080) # 鼠标绝对坐标命令 cmd = [0x57, 0xAB, 0x00, 0x04, 0x07, 0x02] button_val = 1 > 8) & 0xFF low_y = ny & 0xFF high_y = (ny >> 8) & 0xFF scroll = 0x00 data = [button_val, low_x, high_x, low_y, high_y, scroll] sum_val = (sum(cmd) + sum(data)) & 0xFF data.append(sum_val) # 按下 press = cmd + data # 释放 release = press.copy() release[6] = 0x0 sum_val = sum(release[:-1]) & 0xFF release[len(release) - 1] = sum_val while clicks > 0: # 移动并按下键 mserial.write(bytes(press)) # 延时50ms time.sleep(50 / 1000) # mserial.readall() # 释放键 mserial.write(bytes(release)) time.sleep(50 / 1000) # mserial.readall() clicks -= 1 if clicks > 0: time.sleep(interval) # 忽略CH9239回复的消息 mserial.flushInput() return True

注意:其中串口的名称根据设备管理器里面的COM口实际名称来定。一般插上CH340 USB转TTL模块,设备管理器中就能看到。如果没有这一项的话,需要装一个CH340的驱动,这个驱动很常用,很容易找到安装的教程,此处不做赘述。波特率设置可以使用CH9239模块资料中的设置工具设置你想要的波特率。

鼠标绝对移动,不点击:

def hard_moveTo(x, y): global mserial nx = int(x * 4096 / 1920) ny = int(y * 4096 / 1080) # 鼠标绝对坐标命令 cmd = [0x57, 0xAB, 0x00, 0x04, 0x07, 0x02] button_val = 0 low_x = nx & 0xFF high_x = (nx >> 8) & 0xFF low_y = ny & 0xFF high_y = (ny >> 8) & 0xFF scroll = 0x00 data = [button_val, low_x, high_x, low_y, high_y, scroll] sum_val = (sum(cmd) + sum(data)) & 0xFF data.append(sum_val) cmd_move = cmd + data mserial.write(bytes(cmd_move)) mserial.flushInput() return True

然后是按键,因为我们这个脚本只用到了'D','F','ESC'这三个按键,所以其他的按键暂时不管:

# ch9329 键值对 key_map = { 'A': 0x4, 'D': 0x7, 'F': 0x9, 'ESC':0x29, } # ch9329 控制键offset control_key_map = { 'ctrl': 0, 'shift': 1, 'alt': 2, 'win': 3, } def hard_key_write(keys): global key_map, control_key_map l_keys = keys.split('+') v = [] c = [] for k in l_keys: k = k.upper() if k in key_map: v.append(key_map[k]) if k in control_key_map: c.append(control_key_map[k]) cmd = [0x57, 0xab, 0x00, 0x02, 0x08] data = [] ctl_byte = 0x0 for i in c: ctl_byte += 1 4: index = 4 if index < 0: index = 0 box = champ_box[index] center = get_box_center(box) hard_click(center[0], center[1], button=pg.LEFT) return True # 刷新商店 def refresh_shop(): hard_key_write('D') return True # 买经验 def upgrade_champ(): hard_key_write('F') # 一边演,一边等待3-2回合 def pg_wait_stage_3_2(): pic = Image.open(r'scs\3_2.jpg') while True: # pyautogui 模块检测关卡 box = pg.locateOnScreen(pic, confidence=0.9) if box: return True case = random.randint(1,100) # %40 概率游荡 if case < 40: hang_out() case = random.randint(1,100) # %30 概率买1个英雄 if case >= 20 and case < 50: buy_single_champ(random.randint(0, 4)) case = random.randint(1,100) # %1 概率刷新商店 if case == 50: refresh_shop() case = random.randint(1,100) # %10 概率升级 if case >= 80 and case < 90: upgrade_champ() time.sleep(2)

三连告辞(投降):

def get_box_center(box): x = int((box[2] - box[0]) / 2) + box[0] y = int((box[3] - box[1]) / 2) + box[1] return (x, y) def surrender(): global box_settings, box_surrender, box_confirm, thread_check_flag center = get_box_center(box_settings) hard_moveTo(center[0], center[1]) # 点击设置 time.sleep(0.3) hard_click(center[0], center[1]) # hard_key_write('esc') time.sleep(1) center = get_box_center(box_surrender) hard_moveTo(center[0], center[1]) # 发起投降 time.sleep(0.3) hard_click(center[0], center[1]) time.sleep(0.5) center = get_box_center(box_confirm) hard_moveTo(center[0], center[1]) # 确定离开 time.sleep(0.3) hard_click(center[0], center[1]) thread_check_flag = 0 return True

注意:LOL的这些按钮,直接hard_click点击按钮,可能不成功,界面不会显示出来。所以先把鼠标移到图标上,稍微延迟一点时间再进行点击,这样确保点击成功。

等待游戏进程结束:

def pg_wait_surrender_finish(): client_title = 'League of Legends (TM) Client' times = 0 while True: time.sleep(1) times += 1 if pg.getWindowsWithTitle(client_title) == []: break if times % 10 == 0: print("pg_wait_surrender_finish %d sec, still wait client over!"%times) # 检测结算界面是否打开 while True: pic = Image.open(r'scs\end.jpg') box = pg.locateOnScreen(pic, confidence = 0.9) if box: break time.sleep(1) return True

再玩一次:

def pg_play_again(lol_hwnd): global box_play_again, box_empty_area center = convert_lol_to_destop_point(get_box_center(box_play_again), lol_hwnd) hard_moveTo(center[0], center[1]) time.sleep(0.3) hard_click(center[0], center[1]) # 点击再玩一次 move_to_empty_area(lol_hwnd) return True

注意:这部分代码有可能出现点击‘再玩一次’按钮失败的情况,然后导致脚本不能正常执行。可以考虑检测是否出现‘寻找对局’按钮来检测是否点击成功:

def pg_play_again(lol_hwnd): global box_play_again, box_empty_area center = convert_lol_to_destop_point(get_box_center(box_play_again), lol_hwnd) hard_moveTo(center[0], center[1]) time.sleep(0.5) hard_click(center[0], center[1]) # 点击再玩一次 move_to_empty_area(lol_hwnd) # 检测寻找对局按钮 time.sleep(1) pic_find_match = Image.open(r'scs\find_match.jpg') box = pg.locateOnScreen(pic_find_match) while not box: hard_moveTo(center[0], center[1]) time.sleep(0.5) hard_click(center[0], center[1]) # 点击再玩一次 move_to_empty_area(lol_hwnd) time.sleep(1) box = pg.locateOnScreen(pic_find_match) return True 硬件连接

只要电脑上有两个USB口就行,通过杜邦线连接USB转TTL和CH9239模块,如果没有,再买个USB转hub的器件:

完整代码 import time import pyautogui as pg from PIL import Image import serial import serial.tools.list_ports import random mserial = None AutoPalyFlag = True # 0:未进入对局 1:第一阶段 2:剩下的阶段 # 设置 图标 box box_settings = (1890, 870, 1920, 900) # 发起投降 按钮 box box_surrender = (700, 820, 840, 860) # 确认投降 按钮 box box_confirm = (720, 460, 940, 520) # 空白区域 box_empty_area = (700, 0, 800, 10) # 再玩一次 box_play_again = (530, 830, 780, 880) # 商店5个怪的box champ_box = [ (480, 930, 670, 1070), (680, 930, 870, 1070), (880, 930, 1070, 1070), (1080, 930, 1270, 1070), (1290, 930, 1480, 1070), ] # ch9329 键值对 key_map = { 'A': 0x4, 'D': 0x7, 'F': 0x9, 'ESC':0x29, } # ch9329 控制键offset control_key_map = { 'ctrl': 0, 'shift': 1, 'alt': 2, 'win': 3, } def get_button_val(button): if button == pg.LEFT: return 0 if button == pg.RIGHT: return 1 if button == pg.MIDDLE: return 2 def hard_click(x, y, clicks=1, interval=0.0, button=pg.LEFT, duration=0.0): global mserial nx = int(x * 4096 / 1920) ny = int(y * 4096 / 1080) # 鼠标绝对坐标命令 cmd = [0x57, 0xAB, 0x00, 0x04, 0x07, 0x02] button_val = 1 > 8) & 0xFF low_y = ny & 0xFF high_y = (ny >> 8) & 0xFF scroll = 0x00 data = [button_val, low_x, high_x, low_y, high_y, scroll] sum_val = (sum(cmd) + sum(data)) & 0xFF data.append(sum_val) # 按下 press = cmd + data # 释放 release = press.copy() release[6] = 0x0 sum_val = sum(release[:-1]) & 0xFF release[len(release) - 1] = sum_val while clicks > 0: # 移动并按下键 mserial.write(bytes(press)) # 延时50ms time.sleep(50 / 1000) # mserial.readall() # 释放键 mserial.write(bytes(release)) time.sleep(50 / 1000) # mserial.readall() clicks -= 1 if clicks > 0: time.sleep(interval) mserial.flushInput() return True def hard_moveTo(x, y): global mserial nx = int(x * 4096 / 1920) ny = int(y * 4096 / 1080) # 鼠标绝对坐标命令 cmd = [0x57, 0xAB, 0x00, 0x04, 0x07, 0x02] button_val = 0 low_x = nx & 0xFF high_x = (nx >> 8) & 0xFF low_y = ny & 0xFF high_y = (ny >> 8) & 0xFF scroll = 0x00 data = [button_val, low_x, high_x, low_y, high_y, scroll] sum_val = (sum(cmd) + sum(data)) & 0xFF data.append(sum_val) cmd_move = cmd + data mserial.write(bytes(cmd_move)) mserial.flushInput() return True def hard_key_write(keys): global key_map, control_key_map l_keys = keys.split('+') v = [] c = [] for k in l_keys: k = k.upper() if k in key_map: v.append(key_map[k]) if k in control_key_map: c.append(control_key_map[k]) cmd = [0x57, 0xab, 0x00, 0x02, 0x08] data = [] ctl_byte = 0x0 for i in c: ctl_byte += 1 4: index = 4 if index < 0: index = 0 box = champ_box[index] center = get_box_center(box) hard_click(center[0], center[1], button=pg.LEFT) return True # 刷新商店 def refresh_shop(): hard_key_write('D') return True # 提升等级 def upgrade_champ(): hard_key_write('F') return True def get_lol_hwnd(): title = 'League of Legends' w = pg.getWindowsWithTitle(title) for i in w: if i.title == title: w = i break return w def get_available_serial(): l = list(serial.tools.list_ports.comports()) if len(l) < 1: return False port_list = list(l[0]) port = port_list[0] return port def open_serial(): global mserial port = get_available_serial() if not port: return False # mserial = serial.Serial("COM3", 115200, timeout=1) mserial = serial.Serial(port, 115200, timeout=1) return True def get_box_center(box): x = int((box[2] - box[0]) / 2) + box[0] y = int((box[3] - box[1]) / 2) + box[1] return (x, y) def move_to_empty_area(lol_hwnd): global box_empty_area center = convert_lol_to_destop_point(get_box_center(box_empty_area), lol_hwnd) hard_moveTo(center[0], center[1]) return True # 将LOL客户端相对坐标根据LOL客户端位置转换为实际桌面的绝对坐标 def convert_lol_to_destop_point(p, lol_hwnd): x = lol_hwnd.topleft[0] + p[0] y = lol_hwnd.topleft[1] + p[1] return (x, y) def surrender(): global box_settings, box_surrender, box_confirm center = get_box_center(box_settings) hard_moveTo(center[0], center[1]) # 点击设置 time.sleep(0.3) hard_click(center[0], center[1]) # hard_key_write('esc') time.sleep(1) center = get_box_center(box_surrender) hard_moveTo(center[0], center[1]) # 发起投降 time.sleep(0.3) hard_click(center[0], center[1]) time.sleep(0.5) center = get_box_center(box_confirm) hard_moveTo(center[0], center[1]) # 确定离开 time.sleep(0.3) hard_click(center[0], center[1]) return True # 寻找对局 -> 队列中 -> 接受 -> 开始加载 # 寻找对局 -> 队列中 -> 接受 -> 有人拒绝 -> 队列中 def pg_find_match(lol_hwnd): pic_find_match = Image.open(r'scs\find_match.jpg') pic_accept = Image.open(r'scs\accept.jpg') while True: # 点击寻找对局 box = pg.locateOnScreen(pic_find_match, confidence=0.9) if box: p = pg.center(box) hard_click(p[0], p[1]) move_to_empty_area(lol_hwnd) else: time.sleep(1) continue client_title = 'League of Legends (TM) Client' # 队列中 while True: # 接受对局 box3 = pg.locateOnScreen(pic_accept, confidence = 0.9) if box3: p3 = pg.center(box3) hard_click(p3[0], p3[1]) move_to_empty_area(lol_hwnd) time.sleep(5) if pg.getWindowsWithTitle(client_title) != []: return True time.sleep(1) return False def pg_wait_loading(lol_hwnd): pic = Image.open(r'scs\1_1.jpg') while True: # pyautogui 模块检测关卡 box = pg.locateOnScreen(pic, confidence=0.9) if box: return True time.sleep(2) return True def pg_wait_stage_3_2(): pic = Image.open(r'scs\3_2.jpg') while True: # pyautogui 模块检测关卡 box = pg.locateOnScreen(pic, confidence=0.9) if box: return True case = random.randint(1,100) # %40 概率游荡 if case < 40: hang_out() case = random.randint(1,100) # %30 概率买1个英雄 if case >= 20 and case < 50: buy_single_champ(random.randint(0, 4)) case = random.randint(1,100) # %1 概率刷新商店 if case == 50: refresh_shop() case = random.randint(1,100) # %10 概率升级 if case >= 80 and case < 90: upgrade_champ() time.sleep(2) def pg_wait_surrender_finish(): client_title = 'League of Legends (TM) Client' times = 0 while True: time.sleep(1) times += 1 if pg.getWindowsWithTitle(client_title) == []: break if times % 10 == 0: print("pg_wait_surrender_finish %d sec, still wait client over!"%times) # 检测结算界面是否打开 while True: pic = Image.open(r'scs\end.jpg') box = pg.locateOnScreen(pic, confidence = 0.9) if box: break time.sleep(1) return True def pg_play_again(lol_hwnd): global box_play_again, box_empty_area center = convert_lol_to_destop_point(get_box_center(box_play_again), lol_hwnd) hard_moveTo(center[0], center[1]) time.sleep(0.5) hard_click(center[0], center[1]) # 点击再玩一次 move_to_empty_area(lol_hwnd) # 检测寻找对局按钮 time.sleep(1) pic_find_match = Image.open(r'scs\find_match.jpg') box = pg.locateOnScreen(pic_find_match) # 未找到,就重新点击再来一局按钮,然后继续找 while not box: hard_moveTo(center[0], center[1]) time.sleep(0.5) hard_click(center[0], center[1]) # 点击再玩一次 move_to_empty_area(lol_hwnd) time.sleep(1) box = pg.locateOnScreen(pic_find_match) return True def pg_main(): global mserial, AutoPalyFlag open_serial() w = get_lol_hwnd() while AutoPalyFlag: print("[%s] pg_find_match"%time.asctime()) pg_find_match(w) print("[%s] pg_wait_loading"%time.asctime()) pg_wait_loading(w) print("[%s] pg_wait_stage_3_2"%time.asctime()) pg_wait_stage_3_2() print("[%s] surrender"%time.asctime()) surrender() print("[%s] pg_wait_surrender_finish"%time.asctime()) pg_wait_surrender_finish() print("[%s] pg_play_again"%time.asctime()) pg_play_again(w) mserial.close() print('Over') return True if __name__ == '__main__': pg_main()

注意:其中的坐标都是LOL客户端分辨率1600x900,LOL游戏分辨率1980x1080时测试得到的,如果你的电脑设置了不同的分辨率,需要重新截图计算实际的坐标。

实际效果

基本15分钟左右一把,一小时能刷4把,24x4 = 96把,一把两个币,一天192个币。护肝。

 

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3