├── icon.ico ├── ok-baijing.exe ├── readme ├── img.png ├── img_1.png ├── img_2.png └── img_3.png ├── requirements.txt ├── main.py ├── main_gpu.py ├── launcher.py ├── assets ├── images │ ├── 160b9b32-18_46_16_498937_ADBCaptureMethod_1280x720_original.png │ ├── 7285ccac-18_44_07_081327_ADBCaptureMethod_1280x720_original.png │ ├── 7e1885c9-18_55_30_787818_ADBCaptureMethod_1280x720_original.png │ ├── 926732a6-18_20_47_746270_ADBCaptureMethod_1280x720_original.png │ └── 99ef8d1f-18_44_44_114313_ADBCaptureMethod_1280x720_original.png └── result.json ├── compact_log.txt ├── main_debug.py ├── main_debug_console.py ├── task ├── NewAoSkillManXunTask2.py ├── NewAoSkillManXunTask3.py ├── JoinGameTask.py ├── AutoStartCombatTask.py ├── BJTask.py ├── NewAoSkillManXunTask.py ├── DailyTask.py └── NewManXunTask.py ├── .github ├── workflows │ ├── needs-reply.yml │ ├── needs-reply-remove.yml │ └── build-launcher.yml └── ISSUE_TEMPLATE │ └── 报告bug-.md ├── requirements-dev.txt ├── launcher.json ├── .gitignore ├── README.md ├── testpaddle.py ├── config.py ├── main_debug.spec ├── main.spec └── LICENSE.txt /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ok-oldking/ok-baijing/HEAD/icon.ico -------------------------------------------------------------------------------- /ok-baijing.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ok-oldking/ok-baijing/HEAD/ok-baijing.exe -------------------------------------------------------------------------------- /readme/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ok-oldking/ok-baijing/HEAD/readme/img.png -------------------------------------------------------------------------------- /readme/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ok-oldking/ok-baijing/HEAD/readme/img_1.png -------------------------------------------------------------------------------- /readme/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ok-oldking/ok-baijing/HEAD/readme/img_2.png -------------------------------------------------------------------------------- /readme/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ok-oldking/ok-baijing/HEAD/readme/img_3.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ok-oldking/ok-baijing/HEAD/requirements.txt -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from config import config 2 | from ok.OK import OK 3 | 4 | config = config 5 | ok = OK(config) 6 | ok.start() 7 | -------------------------------------------------------------------------------- /main_gpu.py: -------------------------------------------------------------------------------- 1 | from config import config 2 | from ok.OK import OK 3 | 4 | config = config 5 | config['ocr']['lib'] = 'paddleocr' 6 | ok = OK(config) 7 | ok.start() 8 | -------------------------------------------------------------------------------- /launcher.py: -------------------------------------------------------------------------------- 1 | # sys.path.append(os.path.abspath('D:/projects/ok-baijing/python-lib')) 2 | 3 | from config import config 4 | from ok.gui.launcher.Launcher import Launcher 5 | 6 | Launcher(config).start() 7 | -------------------------------------------------------------------------------- /assets/images/160b9b32-18_46_16_498937_ADBCaptureMethod_1280x720_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ok-oldking/ok-baijing/HEAD/assets/images/160b9b32-18_46_16_498937_ADBCaptureMethod_1280x720_original.png -------------------------------------------------------------------------------- /assets/images/7285ccac-18_44_07_081327_ADBCaptureMethod_1280x720_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ok-oldking/ok-baijing/HEAD/assets/images/7285ccac-18_44_07_081327_ADBCaptureMethod_1280x720_original.png -------------------------------------------------------------------------------- /assets/images/7e1885c9-18_55_30_787818_ADBCaptureMethod_1280x720_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ok-oldking/ok-baijing/HEAD/assets/images/7e1885c9-18_55_30_787818_ADBCaptureMethod_1280x720_original.png -------------------------------------------------------------------------------- /assets/images/926732a6-18_20_47_746270_ADBCaptureMethod_1280x720_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ok-oldking/ok-baijing/HEAD/assets/images/926732a6-18_20_47_746270_ADBCaptureMethod_1280x720_original.png -------------------------------------------------------------------------------- /assets/images/99ef8d1f-18_44_44_114313_ADBCaptureMethod_1280x720_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ok-oldking/ok-baijing/HEAD/assets/images/99ef8d1f-18_44_44_114313_ADBCaptureMethod_1280x720_original.png -------------------------------------------------------------------------------- /compact_log.txt: -------------------------------------------------------------------------------- 1 | 2024-06-07T12:37:25.201501700Z : ready compact 'D:\MuMuPlayer-12.0\vms\MuMuPlayer-12.0-0\data.vdi' 2 | this time is dry-run, total blocks: 26755, used blocks: 20611, fragment ratio: 0.0586095 3 | -------------------------------------------------------------------------------- /main_debug.py: -------------------------------------------------------------------------------- 1 | from config import config 2 | from ok.OK import OK 3 | 4 | config = config 5 | config['debug'] = True 6 | config['click_screenshots_folder'] = "click_screenshots" # debug用 点击后截图文件夹] 7 | ok = OK(config) 8 | ok.start() 9 | -------------------------------------------------------------------------------- /main_debug_console.py: -------------------------------------------------------------------------------- 1 | from config import config 2 | from ok.OK import OK 3 | 4 | config = config 5 | config['debug'] = True 6 | config['use_gui'] = False 7 | config['onetime_tasks'][1].enable() 8 | ok = OK(config) 9 | ok.start() 10 | -------------------------------------------------------------------------------- /task/NewAoSkillManXunTask2.py: -------------------------------------------------------------------------------- 1 | from task.NewManXunTask import NewManXunTask 2 | 3 | 4 | class NewAoSkillManXunTask2(NewManXunTask): 5 | 6 | def __init__(self): 7 | super().__init__() 8 | self.route = None 9 | self.name = "循环漫巡凹技能2(独立配置)" 10 | self.description = "跟上面功能一样, 配置独立" 11 | -------------------------------------------------------------------------------- /task/NewAoSkillManXunTask3.py: -------------------------------------------------------------------------------- 1 | from task.NewManXunTask import NewManXunTask 2 | 3 | 4 | class NewAoSkillManXunTask3(NewManXunTask): 5 | 6 | def __init__(self): 7 | super().__init__() 8 | self.route = None 9 | self.name = "循环漫巡凹技能3(独立配置)" 10 | self.description = "跟上面功能一样, 配置独立" 11 | -------------------------------------------------------------------------------- /.github/workflows/needs-reply.yml: -------------------------------------------------------------------------------- 1 | name: Close old issues that need reply 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Close old issues that need reply 12 | uses: dwieeb/needs-reply@v2 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | issue-label: needs-reply -------------------------------------------------------------------------------- /task/JoinGameTask.py: -------------------------------------------------------------------------------- 1 | from task.BJTask import BJTask 2 | 3 | 4 | class JoinGameTask(BJTask): 5 | 6 | def __init__(self): 7 | super().__init__() 8 | self.route = None 9 | self.name = "启动游戏(PC版游戏不太兼容)" 10 | self.description = "打开模拟器,启动游戏,签到,进入主页" 11 | 12 | def run(self): 13 | if self.ensure_main_page(): 14 | self.log_info("进入游戏主页成功!", True) 15 | else: 16 | self.log_error("进入游戏主页失败!") 17 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | typing-extensions>=4.11.0 2 | PySide6-Essentials>=6.6.2 3 | pywin32>=306 4 | typing-extensions>=4.11.0 5 | PySide6-Essentials>=6.7.0 6 | PySideSix-Frameless-Window>=0.3.12 7 | darkdetect>=0.8.0 8 | gitpython>=3.1.43 9 | PySide6-Fluent-Widgets>=1.5.5 10 | numpy>=1.26.4 11 | opencv-python>=4.9.0.80 12 | #optional 13 | rapidocr-openvino>=1.3.24 14 | pycaw==20240210 15 | PyDirectInput>=1.0.4 16 | adbutils>=2.2.1 17 | psutil>=6.0.0 18 | ok-d3dshot>=0.1.5 19 | wmi>=1.5.1 20 | 21 | rapidocr_openvino 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/报告bug-.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: '报告Bug ' 3 | about: 遇到的问题 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: ok-oldking 7 | 8 | --- 9 | 10 | 描述错误: 11 | [请清晰简洁地描述错误是什么] 12 | 13 | 复现步骤 按照以下步骤复现行为: 14 | 15 | 1. 点击 ‘…’ 16 | 2. 向下滚动到 ‘…’ 17 | 3. 看到错误 18 | 19 | 截图 : 20 | 如果适用,添加截图以帮助解释您的问题。 21 | 22 | 脚本软件版本: 23 | [如]1.1.1 24 | 25 | windows操作系统: 26 | [例如] Windows 11 Pro 23H2 22631.3593 27 | 28 | 模拟器版本: 29 | [如使用模拟器]MuMu模拟器12 V3.8.25 (2927) 30 | 31 | 上传日志: 32 | 将脚本安装目录的logs,screenshots,config,click_screenshots打包上传附件 33 | -------------------------------------------------------------------------------- /.github/workflows/needs-reply-remove.yml: -------------------------------------------------------------------------------- 1 | name: Remove needs-reply label 2 | 3 | on: 4 | issue_comment: 5 | types: 6 | - created 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | if: | 12 | github.event.comment.author_association != 'OWNER' && 13 | github.event.comment.author_association != 'COLLABORATOR' 14 | steps: 15 | - name: Remove needs-reply label 16 | uses: octokit/request-action@v2.x 17 | continue-on-error: true 18 | with: 19 | route: DELETE /repos/:repository/issues/:issue/labels/:label 20 | repository: ${{ github.repository }} 21 | issue: ${{ github.event.issue.number }} 22 | label: needs-reply 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /launcher.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "CPU", 4 | "entry": "main.py", 5 | "install_dependencies": [ 6 | "PySide6-Fluent-Widgets>=1.5.5 --no-deps", 7 | "ok-script rapidocr-openvino psutil wmi>=1.5.1 pydirectinput adbutils typing-extensions>=4.11.0 numpy>=1.26.4 opencv-python>=4.9.0.80" 8 | ], 9 | "uninstall_dependencies": [ 10 | ] 11 | }, 12 | { 13 | "name": "GPU(Nvidia RTX >= 3000)", 14 | "entry": "main_gpu.py", 15 | "install_dependencies": [ 16 | "paddlepaddle-gpu==3.0.0b1 -i https://www.paddlepaddle.org.cn/packages/stable/cu123/", 17 | "PySide6-Fluent-Widgets>=1.5.5 --no-deps", 18 | "ok-script paddleocr==2.8.1 psutil wmi>=1.5.1 pydirectinput adbutils typing-extensions>=4.11.0 numpy>=1.26.4 opencv-python>=4.9.0.80" 19 | ], 20 | "uninstall_dependencies": [ 21 | ] 22 | } 23 | ] -------------------------------------------------------------------------------- /task/AutoStartCombatTask.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from ok.ocr.OCR import OCR 4 | from ok.task.TriggerTask import TriggerTask 5 | 6 | 7 | class AutoStartCombatTask(TriggerTask, OCR): 8 | 9 | def __init__(self): 10 | super().__init__() 11 | self.route = None 12 | self.name = "自动点开始战斗" 13 | self.description = "帮助快速过剧情" 14 | self.default_config = {'_enabled': False} 15 | 16 | def run(self): 17 | start_combat = self.ocr(0.85, 0.87, 0.96, 0.94, '开始战斗') 18 | if start_combat: 19 | self.click_box(start_combat) 20 | # self.log_info(start_combat) 21 | self.log_info("点击开始战斗", True) 22 | self.sleep(2) 23 | return True 24 | click_to_continue = self.ocr(0.42, 0.74, 0.58, 0.97, match=re.compile(r"^点击")) 25 | if click_to_continue: 26 | self.click_box(click_to_continue) 27 | return True 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | updates/ 2 | repo/ 3 | tesseract 4 | models 5 | fonts 6 | _internal/ 7 | # Byte-compiled / optimized / DLL files 8 | .idea/ 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | configs/ 13 | python/ 14 | python-launcher-lib/ 15 | python-app-lib/ 16 | ok/ 17 | ok 18 | md5.txt 19 | update.bat 20 | click_screenshots/ 21 | test_scripts/ 22 | screenshots/ 23 | thread_dumps.txt 24 | # C extensions 25 | *.so 26 | *.bat 27 | 28 | # Distribution / packaging 29 | .Python 30 | logs/ 31 | build/ 32 | develop-eggs/ 33 | dist/ 34 | downloads/ 35 | eggs/ 36 | .eggs/ 37 | lib/ 38 | lib64/ 39 | parts/ 40 | sdist/ 41 | var/ 42 | wheels/ 43 | share/python-wheels/ 44 | *.egg-info/ 45 | .installed.cfg 46 | *.egg 47 | MANIFEST 48 | venv/ 49 | 50 | # PyInstaller 51 | # Usually these files are written by a python script from a template 52 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 53 | *.manifest 54 | 55 | # Installer logs 56 | pip-log.txt 57 | pip-delete-this-directory.txt 58 | autohelper 59 | dist.7z 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #  ok_baijing 2 | 3 | 已经退坑, 不再更新, 鸣潮还在维护可以看我仓库 4 | 5 | 白荆自动漫巡辅助脚本 6 | 视频演示 [https://www.bilibili.com/video/BV1K7421f7KT/](https://www.bilibili.com/video/BV1K7421f7KT/) 7 | 8 | [报告BUG](https://github.com/ok-oldking/ok-baijing/issues/new?assignees=ok-oldking&labels=bug&projects=&template=%E6%8A%A5%E5%91%8Abug-.md&title=%5BBUG%5D) 9 | 10 | 11 |  12 | 13 | * 自动日常收菜(清体力,0元购,0元抽,送友情点,喝茶三次等等一共110日常点,然后领日常,领大月卡) 14 | * 可以指定角色凹指定技能个数, 循环n次, 直到刷到技能, 如果没有刷到技能就, 自动投降新开 15 | * 基于[ok-script](https://github.com/ok-oldking/ok-script)开发 16 | * 支持PC版(必须前台,管理员权限), 以及安卓模拟器(推荐,可以挂后台)或使用adb连接的安卓物理机(必须手动改16:9分辨率) 17 | 18 | ## 使用方法 19 | 20 | ### windows版本运行 21 | 22 | 1. 从[Github Release](https://github.com/ok-oldking/ok_baijing/releases)下载最新release版, 双击运行.exe, 杀毒软件可能会报警, 23 | 添加安装目录到白名单 24 | 2. 进入游戏漫巡界面, 设置全部为简化 25 |  26 | 3. 选择任务运行 27 | 28 | ### Python源码运行和开发 29 | 30 | ``` 31 | pip install -r requirements.txt 32 | python main.py 33 | ``` 34 | 35 | * main_debug.py 是开启debug日志的, 以及OverlayView, 并且每次点击都会截图保存在click_screenshot 36 | * main_debug_console.py 是未开启gui界面的版本 37 | * 默认使用CPU进行OCR, 识别耗时大约300-400ms, 如果要使用GPU, 请参见RapidOCR文档, 安装GPU版依赖 38 | 39 | ### 常见的问题 40 | 41 | #### 1. 分辨率支持 42 | 43 | 由于都是按窗口百分比计算的, 只支持16:9,最低720p更低会导致无法正确文字识别 44 | 45 | #### 2. 发现Bug怎么办 46 | 47 | 可以直接提ISSUE, 附上截图和日志, 日志在logs文件夹, 错误截图在screenshot里 48 | 49 | #### 3. 是否免费 50 | 51 | 一直免费, 我还没退坑的话, 就会一直更新 52 | 53 | 54 | -------------------------------------------------------------------------------- /testpaddle.py: -------------------------------------------------------------------------------- 1 | import paddle 2 | from paddleocr import PaddleOCR 3 | 4 | print(paddle.utils.run_check()) 5 | gpu_available = paddle.device.is_compiled_with_cuda() 6 | print("GPU available:", gpu_available) 7 | if gpu_available: 8 | place = paddle.CUDAPlace(0) # Use the first GPU 9 | print("Using GPU:", place) 10 | else: 11 | print("Not using GPU") 12 | 13 | import cv2 14 | import paddle 15 | import time 16 | 17 | # Initialize PaddleOCR 18 | # ocr = paddleocr.OCR(use_gpu=True) 19 | ocr = PaddleOCR(use_angle_cls=False, lang="ch", use_gpu=True) 20 | # Read the image using OpenCV 21 | image_path = 'a.png' 22 | image = cv2.imread(image_path) 23 | 24 | # Convert the image to RGB (PaddleOCR expects RGB images) 25 | image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) 26 | 27 | # Run PaddleOCR 10 times and measure the time taken 28 | total_time = 0 29 | result = None 30 | for i in range(10): 31 | start_time = time.time() 32 | result = ocr.ocr(image_rgb, det=True, 33 | rec=True, 34 | cls=False) 35 | end_time = time.time() 36 | elapsed_time = (end_time - start_time) * 1000 # Convert to milliseconds 37 | total_time += elapsed_time 38 | print(f"Run {i + 1}: {elapsed_time:.2f} ms") 39 | 40 | # Calculate and print the average time 41 | average_time = total_time / 10 42 | print(f"Average time per OCR: {average_time:.2f} ms") 43 | 44 | # Check if PaddlePaddle is using the GPU 45 | if paddle.device.is_compiled_with_cuda(): 46 | print("PaddlePaddle is using the GPU.") 47 | else: 48 | print("PaddlePaddle is not using the GPU.") 49 | 50 | print(result) 51 | -------------------------------------------------------------------------------- /.github/workflows/build-launcher.yml: -------------------------------------------------------------------------------- 1 | name: Build Windows Executable 2 | 3 | on: 4 | push: 5 | # Sequence of patterns matched against refs/tags 6 | tags: 7 | - 'v*' 8 | 9 | jobs: 10 | build: 11 | name: Build exe with launcher 12 | runs-on: windows-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | name: Checkout code 16 | with: 17 | fetch-depth: 0 # Important: fetch all history for all tags and branches 18 | 19 | - name: Get Changes between Tags 20 | id: changes 21 | uses: simbo/changes-between-tags-action@v1 22 | with: 23 | validate-tag: false 24 | 25 | - name: Get tag name 26 | id: tagName 27 | uses: olegtarasov/get-tag@v2.1.3 28 | 29 | - name: Set up Python 30 | uses: actions/setup-python@v2 31 | with: 32 | python-version: '3.11' # Use the version of Python you need 33 | 34 | - name: Install Dependencies 35 | run: | 36 | python -m pip install --upgrade pip 37 | pip install -r requirements.txt 38 | 39 | - name: Build Executable 40 | run: | 41 | echo "tag: ${{ steps.changes.outputs.tag }}" 42 | echo "changes: ${{ steps.changes.outputs.changes }}" 43 | python -m ok.update.package_launcher ${{ steps.tagName.outputs.tag }} CPU 44 | mv dist ok-baijing 45 | 7z a -t7z -r "ok-baijing-release-${{ steps.tagName.outputs.tag }}.7z" "ok-baijing" 46 | 47 | shell: pwsh 48 | 49 | - name: Create Release 50 | id: create_release 51 | uses: actions/create-release@v1 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | with: 55 | tag_name: ${{ github.ref }} 56 | release_name: Release ${{ github.ref }} 57 | body: | 58 | 下载release.7z文件然后解压缩后运行, 如果需要查找问题 59 | 最好使用模拟器运行安卓版,可以挂后台,稳定。PC版游戏可能兼容性较差,无法挂后台。 60 | 更新内容: 61 | ${{ steps.changes.outputs.changes }} 62 | draft: false 63 | prerelease: true 64 | 65 | - name: upload-win 66 | uses: actions/upload-release-asset@v1 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | with: 70 | upload_url: ${{ steps.create_release.outputs.upload_url }} 71 | asset_path: ./ok-baijing-release-${{ steps.tagName.outputs.tag }}.7z 72 | asset_name: ok-baijing-release-${{ steps.tagName.outputs.tag }}.7z 73 | asset_content_type: application/zip 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | version = "v1.5.15" 5 | 6 | 7 | def calculate_pc_exe_path(running_path): 8 | return None 9 | 10 | 11 | config = { 12 | 'debug': False, # Optional, default: False 13 | 'use_gui': True, 14 | 'gui_icon': 'icon.ico', 15 | 'ocr': { 16 | 'lib': 'rapidocr_openvino' 17 | # 'lib': 'rapidocr_openvino' 18 | }, 19 | # required if using feature detection 20 | 'template_matching': { 21 | 'coco_feature_json': os.path.join('assets', 'result.json'), 22 | 'default_horizontal_variance': 0.01, 23 | 'default_vertical_variance': 0.01, 24 | 'default_threshold': 0.9, 25 | }, 26 | 'windows': { # required when supporting windows game 27 | 'title': re.compile(r'^白荆回廊'), 28 | 'exe': 'GateMK-Win64-Shipping.exe', 29 | 'calculate_pc_exe_path': calculate_pc_exe_path, 30 | # 'interaction': 'PostMessage', 31 | 'can_bit_blt': True, # default false, opengl games does not support bit_blt 32 | 'bit_blt_render_full': True 33 | }, 34 | 'adb': { 35 | 'packages': ['com.tencent.gate'] 36 | }, 37 | 'analytics': { 38 | 'report_url': 'https://okreport.ok-script.com/report' 39 | }, 40 | 'git_update': {'sources': [{ 41 | 'name': 'Global', 42 | 'git_url': 'https://github.com/ok-oldking/ok-baijing', 43 | 'pip_url': 'https://pypi.org/simple/' 44 | }, { 45 | 'name': 'China', 46 | 'git_url': 'https://gitee.com/ok-olding/ok_baijing', 47 | 'pip_url': 'https://mirrors.cloud.tencent.com/pypi/simple' 48 | }]}, 49 | 'about': """ 50 |
免费开源软件 https://github.com/ok-oldking/ok-baijing>
52 | 53 |视频演示 https://www.bilibili.com/video/BV1K7421f7KT/
54 |QQ群:594495691
55 | """, 56 | 'supported_resolution': { 57 | 'ratio': '16:9', 58 | 'min_size': (1280, 720) 59 | }, 60 | 'supported_screen_ratio': '16:9', 61 | 'screenshots_folder': "screenshots", 62 | 'gui_title': 'OK白荆', # Optional 63 | 'version': version, 64 | 'locale': 'zh_CN', 65 | 'onetime_tasks': [ # tasks to execute 66 | ['task.DailyTask', 'DailyTask'], 67 | ['task.JoinGameTask', 'JoinGameTask'], 68 | ], 'trigger_tasks': [ 69 | ['task.AutoStartCombatTask', 'AutoStartCombatTask'], 70 | ] 71 | } 72 | -------------------------------------------------------------------------------- /main_debug.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | from pathlib import Path 3 | import rapidocr_openvino 4 | 5 | 6 | block_cipher = None 7 | 8 | package_name = 'rapidocr_openvino' 9 | install_dir = Path(rapidocr_openvino.__file__).resolve().parent 10 | 11 | onnx_paths = list(install_dir.rglob('*.onnx')) + list(install_dir.rglob('*.txt')) 12 | yaml_paths = list(install_dir.rglob('*.yaml')) 13 | 14 | onnx_add_data = [(str(v.parent), f'{package_name}/{v.parent.name}') 15 | for v in onnx_paths] 16 | 17 | yaml_add_data = [] 18 | for v in yaml_paths: 19 | if package_name == v.parent.name: 20 | yaml_add_data.append((str(v.parent / '*.yaml'), package_name)) 21 | else: 22 | yaml_add_data.append( 23 | (str(v.parent / '*.yaml'), f'{package_name}/{v.parent.name}')) 24 | 25 | import openvino 26 | 27 | block_cipher = None 28 | 29 | package_name = 'openvino' 30 | install_dir = Path(openvino.__file__).resolve().parent 31 | 32 | openvino_dll_path = list(install_dir.rglob('openvino_intel_cpu_plugin.dll')) + list(install_dir.rglob('openvino_onnx_frontend.dll')) 33 | 34 | 35 | # Modified list comprehension with a condition check 36 | openvino_add_data = [(str(v), f'{package_name}/{v.parent.name}') 37 | for v in openvino_dll_path] 38 | 39 | print(f'openvino_add_data {openvino_add_data}') 40 | add_data = list(set(yaml_add_data + onnx_add_data + openvino_add_data)) 41 | 42 | excludes = ['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter', 'resources', 'matplotlib','numpy.lib'] 43 | add_data.append(('icon.ico', '.')) 44 | 45 | def list_files(directory, prefix=''): 46 | file_list = [] 47 | for root, dirs, files in os.walk(directory): 48 | for filename in files: 49 | # Create the full filepath by joining root with the filename 50 | filepath = os.path.join(root, filename) 51 | # Create the relative path for the file to be used in the spec datas 52 | relative_path = os.path.relpath(filepath, prefix) 53 | folder_path = os.path.dirname(relative_path) 54 | # Append the tuple (full filepath, relative path) to the file list 55 | file_list.append((filepath, folder_path)) 56 | return file_list 57 | 58 | if os.path.exists('assets'): 59 | root_folder = os.getcwd() # Get the current working directory 60 | assets = list_files(os.path.join(root_folder, 'assets'), root_folder) 61 | add_data += assets 62 | 63 | print(f"add_data {add_data}") 64 | 65 | a = Analysis( 66 | ['main_debug.py'], 67 | pathex=[], 68 | binaries=[], 69 | datas=add_data, 70 | hiddenimports=[], 71 | hookspath=[], 72 | hooksconfig={}, 73 | runtime_hooks=[], 74 | excludes=[], 75 | cipher=block_cipher, 76 | noarchive=False, 77 | noconsole=True, 78 | ) 79 | 80 | 81 | # List of patterns to exclude 82 | exclude_patterns = ['opencv_videoio_ffmpeg', 'opengl32sw.dll', 'Qt6Quick.dll','Qt6Pdf.dll','Qt6Qml.dll','Qt6OpenGL.dll','Qt6Network.dll','Qt6QmlModels.dll','Qt6VirtualKeyboard.dll','QtNetwork.pyd' 83 | ,'openvino_pytorch_frontend.dll','openvino_tensorflow_frontend.dll','py_tensorflow_frontend.cp311-win_amd64.pyd','py_pytorch_frontend.cp311-win_amd64.pyd', 84 | ] 85 | 86 | 87 | # Optimized list comprehension using any() with a generator expression 88 | a.binaries = [x for x in a.binaries if not any(pattern in x[0] for pattern in exclude_patterns)] 89 | 90 | print(f'a.binaries {a.binaries}') 91 | 92 | pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) 93 | 94 | exe = EXE( 95 | pyz, 96 | a.scripts, 97 | [], 98 | exclude_binaries=True, 99 | name='ok-baijing', 100 | icon='icon.ico', 101 | debug=False, 102 | bootloader_ignore_signals=False, 103 | strip=False, 104 | upx=True, 105 | console=True, 106 | disable_windowed_traceback=False, 107 | argv_emulation=False, 108 | target_arch=None, 109 | codesign_identity=None, 110 | entitlements_file=None, 111 | ) 112 | 113 | coll = COLLECT( 114 | exe, 115 | a.binaries, 116 | a.datas, 117 | strip=False, 118 | upx=True, 119 | upx_exclude=[], 120 | name='bundle', 121 | ) 122 | 123 | -------------------------------------------------------------------------------- /assets/result.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "width": 1280, 5 | "height": 720, 6 | "id": 0, 7 | "file_name": "images\\926732a6-18_20_47_746270_ADBCaptureMethod_1280x720_original.png" 8 | }, 9 | { 10 | "width": 1280, 11 | "height": 720, 12 | "id": 1, 13 | "file_name": "images\\7285ccac-18_44_07_081327_ADBCaptureMethod_1280x720_original.png" 14 | }, 15 | { 16 | "width": 1280, 17 | "height": 720, 18 | "id": 2, 19 | "file_name": "images\\99ef8d1f-18_44_44_114313_ADBCaptureMethod_1280x720_original.png" 20 | }, 21 | { 22 | "width": 1280, 23 | "height": 720, 24 | "id": 3, 25 | "file_name": "images\\160b9b32-18_46_16_498937_ADBCaptureMethod_1280x720_original.png" 26 | }, 27 | { 28 | "width": 1280, 29 | "height": 720, 30 | "id": 4, 31 | "file_name": "images\\7e1885c9-18_55_30_787818_ADBCaptureMethod_1280x720_original.png" 32 | } 33 | ], 34 | "categories": [ 35 | { 36 | "id": 0, 37 | "name": "assist_laohen_plus" 38 | }, 39 | { 40 | "id": 1, 41 | "name": "box_qiandao" 42 | }, 43 | { 44 | "id": 2, 45 | "name": "click_to_continue" 46 | }, 47 | { 48 | "id": 3, 49 | "name": "go_home" 50 | }, 51 | { 52 | "id": 4, 53 | "name": "hecha_chat" 54 | }, 55 | { 56 | "id": 5, 57 | "name": "hecha_love" 58 | }, 59 | { 60 | "id": 6, 61 | "name": "hecha_question" 62 | }, 63 | { 64 | "id": 7, 65 | "name": "main_screen_feature" 66 | }, 67 | { 68 | "id": 8, 69 | "name": "main_screen_menu" 70 | }, 71 | { 72 | "id": 9, 73 | "name": "manpo_90" 74 | }, 75 | { 76 | "id": 10, 77 | "name": "start_combat" 78 | }, 79 | { 80 | "id": 11, 81 | "name": "start_screen_feature" 82 | } 83 | ], 84 | "annotations": [ 85 | { 86 | "id": 0, 87 | "image_id": 0, 88 | "category_id": 11, 89 | "segmentation": [], 90 | "bbox": [ 91 | 42.40976183724836, 92 | 586.2203340008476, 93 | 42.7602557367298, 94 | 58.181987313910625 95 | ], 96 | "ignore": 0, 97 | "iscrowd": 0, 98 | "area": 2487.8766568139877 99 | }, 100 | { 101 | "id": 1, 102 | "image_id": 1, 103 | "category_id": 5, 104 | "segmentation": [], 105 | "bbox": [ 106 | 926.0, 107 | 445.0, 108 | 20.0, 109 | 22.99999999999999 110 | ], 111 | "ignore": 0, 112 | "iscrowd": 0, 113 | "area": 459.9999999999998 114 | }, 115 | { 116 | "id": 2, 117 | "image_id": 1, 118 | "category_id": 6, 119 | "segmentation": [], 120 | "bbox": [ 121 | 925.0, 122 | 313.0, 123 | 18.0, 124 | 22.00000000000001 125 | ], 126 | "ignore": 0, 127 | "iscrowd": 0, 128 | "area": 396.00000000000017 129 | }, 130 | { 131 | "id": 3, 132 | "image_id": 2, 133 | "category_id": 4, 134 | "segmentation": [], 135 | "bbox": [ 136 | 876.6639574257163, 137 | 294.02386400784167, 138 | 36.69080545349552, 139 | 33.3039618731728 140 | ], 141 | "ignore": 0, 142 | "iscrowd": 0, 143 | "area": 1221.9491859192156 144 | }, 145 | { 146 | "id": 4, 147 | "image_id": 3, 148 | "category_id": 7, 149 | "segmentation": [], 150 | "bbox": [ 151 | 1086.4536243180053, 152 | 661.4497272018693, 153 | 141.6679657053777, 154 | 30.927513639906433 155 | ], 156 | "ignore": 0, 157 | "iscrowd": 0, 158 | "area": 4381.437941690865 159 | }, 160 | { 161 | "id": 5, 162 | "image_id": 4, 163 | "category_id": 3, 164 | "segmentation": [], 165 | "bbox": [ 166 | 211.12987592117952, 167 | 17.77045047833575, 168 | 46.11855005091897, 169 | 36.38711288421131 170 | ], 171 | "ignore": 0, 172 | "iscrowd": 0, 173 | "area": 1678.120886758938 174 | }, 175 | { 176 | "id": 6, 177 | "image_id": 4, 178 | "category_id": 9, 179 | "segmentation": [], 180 | "bbox": [ 181 | 171.3579153268091, 182 | 281.78857187075266, 183 | 49.08029179730831, 184 | 17.770450478335743 185 | ], 186 | "ignore": 0, 187 | "iscrowd": 0, 188 | "area": 872.1788948463353 189 | } 190 | ], 191 | "info": { 192 | "year": 2024, 193 | "version": "1.0", 194 | "description": "", 195 | "contributor": "Label Studio", 196 | "url": "", 197 | "date_created": "2024-06-17 13:56:43.333196" 198 | } 199 | } -------------------------------------------------------------------------------- /main.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: pythzon ; coding: utf-8 -*- 2 | from pathlib import Path 3 | import importlib.util 4 | block_cipher = None 5 | 6 | def check_package_exists(package_name): 7 | package_spec = importlib.util.find_spec(package_name) 8 | return package_spec is not None 9 | 10 | # Example usage: 11 | if check_package_exists('rapidocr_openvino'): 12 | print("rapidocr_openvino exists") 13 | 14 | import rapidocr_openvino 15 | 16 | package_name = 'rapidocr_openvino' 17 | install_dir = Path(rapidocr_openvino.__file__).resolve().parent 18 | 19 | onnx_paths = list(install_dir.rglob('*.onnx')) + list(install_dir.rglob('*.txt')) 20 | yaml_paths = list(install_dir.rglob('*.yaml')) 21 | 22 | onnx_add_data = [(str(v.parent), f'{package_name}/{v.parent.name}') 23 | for v in onnx_paths] 24 | 25 | yaml_add_data = [] 26 | for v in yaml_paths: 27 | if package_name == v.parent.name: 28 | yaml_add_data.append((str(v.parent / '*.yaml'), package_name)) 29 | else: 30 | yaml_add_data.append( 31 | (str(v.parent / '*.yaml'), f'{package_name}/{v.parent.name}')) 32 | 33 | import openvino 34 | 35 | 36 | package_name = 'openvino' 37 | install_dir = Path(openvino.__file__).resolve().parent 38 | 39 | openvino_dll_path = list(install_dir.rglob('openvino_intel_cpu_plugin.dll')) + list(install_dir.rglob('openvino_onnx_frontend.dll')) 40 | 41 | 42 | # Modified list comprehension with a condition check 43 | openvino_add_data = [(str(v), f'{package_name}/{v.parent.name}') 44 | for v in openvino_dll_path] 45 | 46 | print(f'openvino_add_data {openvino_add_data}') 47 | add_data = list(set(yaml_add_data + onnx_add_data + openvino_add_data)) 48 | else: 49 | add_data = [] 50 | 51 | excludes = ['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter', 'resources', 'matplotlib','numpy.lib'] 52 | add_data.append(('icon.ico', '.')) 53 | 54 | import ok 55 | ok_dir = Path(ok.__file__).resolve().parent 56 | binaries = os.path.join(ok_dir, 'binaries', '*') 57 | print(f'ok_dir {ok_dir}') 58 | add_data.append((binaries, 'ok/binaries')) 59 | 60 | def list_files(directory, prefix=''): 61 | file_list = [] 62 | for root, dirs, files in os.walk(directory): 63 | for filename in files: 64 | # Create the full filepath by joining root with the filename 65 | filepath = os.path.join(root, filename) 66 | # Create the relative path for the file to be used in the spec datas 67 | relative_path = os.path.relpath(filepath, prefix) 68 | folder_path = os.path.dirname(relative_path) 69 | # Append the tuple (full filepath, relative path) to the file list 70 | file_list.append((filepath, folder_path)) 71 | return file_list 72 | 73 | if os.path.exists('assets'): 74 | root_folder = os.getcwd() # Get the current working directory 75 | assets = list_files(os.path.join(root_folder, 'assets'), root_folder) 76 | add_data += assets 77 | 78 | print(f"add_data {add_data}") 79 | 80 | a = Analysis( 81 | ['main.py'], 82 | pathex=[], 83 | binaries=[], 84 | datas=add_data, 85 | hiddenimports=[], 86 | hookspath=[], 87 | hooksconfig={}, 88 | runtime_hooks=[], 89 | excludes=[], 90 | cipher=block_cipher, 91 | noarchive=False, 92 | noconsole=True, 93 | ) 94 | 95 | 96 | # List of patterns to exclude 97 | exclude_patterns = ['opencv_videoio_ffmpeg', 'opengl32sw.dll', 'Qt6Quick.dll','Qt6Pdf.dll','Qt6Qml.dll','Qt6OpenGL.dll','Qt6Network.dll','Qt6QmlModels.dll','Qt6VirtualKeyboard.dll','QtNetwork.pyd' 98 | ,'openvino_pytorch_frontend.dll','openvino_tensorflow_frontend.dll','py_tensorflow_frontend.cp311-win_amd64.pyd','py_pytorch_frontend.cp311-win_amd64.pyd', 99 | ] 100 | 101 | 102 | # Optimized list comprehension using any() with a generator expression 103 | a.binaries = [x for x in a.binaries if not any(pattern in x[0] for pattern in exclude_patterns)] 104 | 105 | print(f'a.binaries {a.binaries}') 106 | 107 | pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) 108 | 109 | from config import config 110 | 111 | exe = EXE( 112 | pyz, 113 | a.scripts, 114 | [], 115 | exclude_binaries=True, 116 | name='ok-baijing', 117 | icon='icon.ico', 118 | debug=False, 119 | bootloader_ignore_signals=False, 120 | strip=False, 121 | upx=True, 122 | console=False, 123 | disable_windowed_traceback=False, 124 | argv_emulation=False, 125 | target_arch=None, 126 | codesign_identity=None, 127 | entitlements_file=None, 128 | ) 129 | 130 | coll = COLLECT( 131 | exe, 132 | a.binaries, 133 | a.datas, 134 | strip=False, 135 | upx=True, 136 | upx_exclude=[], 137 | name='bundle', 138 | ) 139 | 140 | -------------------------------------------------------------------------------- /task/BJTask.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from ok.color.Color import white_color 4 | from ok.feature.Box import find_box_by_name 5 | from ok.feature.FindFeature import FindFeature 6 | from ok.ocr.OCR import OCR 7 | from ok.task.BaseTask import BaseTask 8 | 9 | 10 | class BJTask(BaseTask, OCR, FindFeature): 11 | def __init__(self): 12 | super(BJTask, self).__init__() 13 | self.main_menu_buttons = None 14 | self.menu_name_list = ['外勤作战', '回廊漫巡'] 15 | self.auto_combat_timeout = 600 16 | 17 | def ensure_main_page(self): 18 | if self.go_home_now(): 19 | return True 20 | result = self.wait_until(self.check_until_main, time_out=120) 21 | if not result: 22 | self.log_error('无法进入到主页,请手动进入游戏主页!', notify=True) 23 | return False 24 | elif isinstance(result, str): 25 | self.log_error(result, notify=True) 26 | return False 27 | else: 28 | self.log_info("进入主页成功", notify=True) 29 | return True 30 | 31 | def click_to_continue_wait(self, time_out=0): 32 | while self.wait_click_ocr(0.42, 0.74, 0.58, 0.97, match=re.compile(r"^点击"), time_out=time_out): 33 | pass 34 | 35 | def click_to_continue(self): 36 | click_to_continue = self.ocr(0.42, 0.74, 0.58, 0.97, match=re.compile(r"^点击")) 37 | if click_to_continue: 38 | self.click_box(click_to_continue) 39 | return True 40 | 41 | def go_home_wait(self): 42 | self.wait_click_feature('go_home') 43 | return self.wait_main() 44 | 45 | def wait_main(self): 46 | return self.wait_until(self.find_world) 47 | 48 | def go_home_now(self): 49 | go_home = self.find_feature('go_home', threshold=0.92) 50 | if go_home: 51 | self.log_info("返回主页") 52 | self.click_box(go_home) 53 | self.sleep(4) 54 | return True 55 | 56 | def choose_main_menu(self, name): 57 | self.go_into_menu(name) 58 | 59 | def check_until_main(self): 60 | self.info['检查主页次数'] = self.info.get("检查主页次数", 1) + 1 61 | self.log_debug(f'check check_until_main {self.info.get("检查主页次数")}') 62 | main = self.ocr(.90, 0.91, 0.97, 0.96, match="功能菜单", log=True) 63 | self.log_debug(f'found main menu {main}') 64 | if main: 65 | while True: 66 | task = self.find_world() 67 | if task: 68 | break 69 | if self.click_to_continue(): 70 | continue 71 | qiandao_lingqu = self.ocr(0.2, 0.6, 0.8, match="可领取", log=True) 72 | if qiandao_lingqu: 73 | self.click_box(qiandao_lingqu, relative_y=-2) 74 | self.sleep(2) 75 | self.click_relative(0.4, 0.05) 76 | self.sleep(2) 77 | continue 78 | else: 79 | self.log_debug(f'found main_screen_feature {main}') 80 | self.click_relative(0.4, 0.05) 81 | self.sleep(2) 82 | self.click_to_continue() 83 | self.sleep(3) # wait for animation 84 | if self.find_world(): 85 | return True 86 | start = self.find_one('start_screen_feature', 87 | threshold=0.9, use_gray_scale=True) 88 | self.log_debug(f'found start feature {start}') 89 | if start: 90 | boxes = self.ocr(0.5, 0.5) 91 | if find_box_by_name(boxes, "微信登录"): 92 | self.log_info('需要登陆账号', notify=True) 93 | self.disable() 94 | return "需要登陆账号" 95 | if box := find_box_by_name(boxes, "点击进入游戏"): 96 | self.click_box(box) 97 | return False 98 | 99 | self.log_debug(f'found start_screen_feature {start}') 100 | self.click_relative(0.88, 0.5) 101 | self.sleep(2) 102 | return False 103 | self.click_to_continue() 104 | 105 | def do_find_world(self): 106 | world = self.ocr(box=self.main_menu_zone, match=re.compile(r"世界"), log=True) 107 | if world: 108 | white_color_percent = self.calculate_color_percentage(white_color, world[0]) 109 | self.log_debug(f'world white percent {white_color_percent}') 110 | return white_color_percent > 0.05 111 | 112 | def find_world(self): 113 | if self.do_find_world(): 114 | self.sleep(2) 115 | return self.do_find_world() 116 | 117 | def go_into_menu(self, menu, confirm=False): 118 | self.wait_click_ocr(0.9, 0.9, match="功能菜单") 119 | self.wait_click_ocr(0.6, 0.1, to_y=0.8, match=menu) 120 | if confirm: 121 | self.wait_confirm() 122 | 123 | def wait_confirm(self): 124 | self.wait_click_ocr(0.5, 0.5, to_x=0.8, to_y=0.8, match=re.compile('^确')) 125 | 126 | @property 127 | def star_combat_zone(self): 128 | return self.box_of_screen(0.8, 0.8, width=0.2, height=0.2, name="star_combat_zone") 129 | 130 | @property 131 | def main_menu_zone(self): 132 | return self.box_of_screen(0.62, 0.60, 0.97, 0.87, name="主页菜单区域") 133 | # return self.box_of_screen(0, 0, 1, 1, name="主页菜单区域") 134 | 135 | def check_is_main(self): 136 | return self.find_world() 137 | 138 | def main_go_to_manxun(self): 139 | self.go_into_menu('回廊漫巡') 140 | -------------------------------------------------------------------------------- /task/NewAoSkillManXunTask.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from typing_extensions import override 4 | 5 | from ok.feature.Box import find_box_by_name 6 | from ok.task.TaskExecutor import FinishedException, TaskDisabledException 7 | from task.NewManXunTask import NewManXunTask, find_index 8 | 9 | 10 | class NewAoSkillManXunTask(NewManXunTask): 11 | 12 | def __init__(self): 13 | super().__init__() 14 | self.route = None 15 | self.name = "循环漫巡凹技能" 16 | self.description = "烙痕使用上次编组, 只支持福利院" 17 | self.super_config = self.default_config 18 | self.default_config = {'角色名': '岑缨', '路线': '福利院', '支援烙痕': '于火光中', '支援烙痕类型': '专精', 19 | '漫巡次数': 5, 20 | '目标技能': ['职业联动', '针对打击', '奉献'], '目标技能个数': 3} 21 | self.default_config = {**self.default_config, **self.super_config} 22 | self.config_description["目标技能"] = "部分匹配, 中间带标点的,留半边,如'职业联动*菱形',写'职业联动'" 23 | self.config_description["目标技能个数"] = "目标技能加起来一共刷多少个, 大于等于" 24 | self.config_description["漫巡次数"] = "刷多少次, 直到技能满足要求" 25 | self.config_description["支援烙痕"] = '部分匹配, 如"于火光中[蛋生]" 可以填"于火光中"' 26 | self.config_description["角色名"] = '部分匹配即可' 27 | self.config_description["路线"] = '只支持福利院' 28 | self.config_description["支援烙痕类型"] = '一定要匹配, 否则刷不到' 29 | self.config_type["支援烙痕类型"] = {'type': "drop_down", 'options': self.stats_seq} 30 | self.pause_combat_message = "成功刷到目标技能, 暂停" 31 | 32 | @override 33 | def run(self): 34 | self.log_info(f'run, config {self.config}') 35 | i = 0 36 | while True: 37 | i = i + 1 38 | if i > self.config.get('漫巡次数'): 39 | self.log_info(f'已经第{self.config.get("漫巡次数")}次,没刷到指定技能', notify=True) 40 | break 41 | self.info.clear() 42 | self.route = None 43 | self.info['目前漫巡次数'] = i 44 | self.log_info(f'凹技能进行第{self.info["目前漫巡次数"]} 次', notify=True) 45 | if not self.loop_manxun(): 46 | break 47 | self.log_info("凹技能任务结束", notify=True) 48 | 49 | def confirm_generate(self): 50 | if self.info.get('已获得目标技能个数', 0) < self.config['目标技能个数']: 51 | return True 52 | 53 | def loop_manxun(self): 54 | is_main = self.check_is_main() 55 | if not is_main and not self.check_is_manxun_ui(): 56 | boxes = self.ocr(box=self.box_of_screen(0.2, 0.5, width=0.8, height=0.3, name="漫巡路线检测区域"), 57 | match=self.config['路线']) 58 | if boxes: 59 | self.route = boxes[0] 60 | self.log_debug(f'在漫巡路线选择界面 {self.route}') 61 | if not self.route: 62 | self.log_error("必须从主界面或漫巡界面开始", notify=True) 63 | self.screenshot("必须从主界面或漫巡界面开始") 64 | return False 65 | if is_main or self.route: 66 | if not self.enter_manxun(): 67 | self.start_manxun() 68 | self.wait_click_ocr(0.42, 0.63, 0.62, 0.79, match='漫巡开始', time_out=60) 69 | while True: 70 | try: 71 | self.loop() 72 | except (FinishedException, TaskDisabledException): 73 | return True 74 | except Exception as e: 75 | self.screenshot(f"运行异常{e}") 76 | self.log_error(f"运行异常,已暂停:", e, True) 77 | self.pause() 78 | 79 | def start_manxun(self): 80 | boxes = self.ocr(box=self.bottom_button_zone) 81 | manxun_start = find_box_by_name(boxes, "漫巡开始") 82 | if not manxun_start: 83 | self.select_char() 84 | manxun_start = self.ocr(box=self.bottom_button_zone, match="漫巡开始") 85 | self.choose_assist_laohen() 86 | self.sleep(3) 87 | self.click_box(manxun_start) 88 | 89 | def if_skip_battle(self): 90 | if self.info.get('漫巡深度', 0) < self.config['深度等级最多提升到']: 91 | self.log_debug(f"未达到预定深度, 不跳过战斗") 92 | self.pause_combat_message = "未达到预定深度, 不跳过战斗" 93 | return False 94 | 95 | self.log_debug(f"检测技能是否满足条件 {self.info.get('已获得目标技能个数', 0)} {self.info.get('获得技能', [])}") 96 | if self.info.get('已获得目标技能个数', 0) >= self.config['目标技能个数']: 97 | self.pause_combat_message = "成功刷到目标技能, 暂停" 98 | return False 99 | else: 100 | return True 101 | 102 | def check_skills(self): 103 | current_skills = self.info['获得技能'] 104 | current_count = 0 105 | skills = {} 106 | if current_skills: 107 | for skill in current_skills: 108 | for target_skill in self.config['目标技能']: 109 | if target_skill in skill: 110 | current_count += 1 111 | skills[skill] = skills.get(skill, 0) + 1 112 | if current_count != self.info.get('已获得目标技能个数', 0): 113 | self.info['已获得目标技能个数'] = current_count 114 | self.info['已获取的目标技能'] = skills 115 | self.notification(f"已经获取到 {current_count}个目标技能 {skills}") 116 | if current_count >= self.config['目标技能个数']: 117 | self.pause() 118 | 119 | def select_char(self): 120 | char_name = self.wait_until( 121 | lambda: self.ocr(box=self.box_of_screen(0.4, 0.4, width=0.6, height=0.5, name="角色检测区域"), 122 | match=re.compile(f"{self.config['角色名']}")), 123 | time_out=60, 124 | post_action=lambda: self.swipe_relative(0.8, 0.5, 0.5, 0.5, 1)) 125 | if not char_name: 126 | raise Exception(f'找不到角色 {self.config["角色名"]}') 127 | self.click_box(char_name) 128 | next_step = self.wait_click_box(lambda: self.ocr(box=self.bottom_button_zone, match="下一步")) 129 | self.wait_click_box(lambda: self.ocr(box=self.top_right_button_zone, match="上次编队")) 130 | self.sleep(0.5) 131 | self.click_box(next_step) 132 | self.wait_click_box(lambda: self.ocr(box=self.top_right_button_zone, match="上次编组")) 133 | self.sleep(3) 134 | 135 | def choose_assist_laohen(self): 136 | boxes = self.ocr(box=self.box_of_screen(0.5, 0.5, width=0.5, height=0.5, name="支援记忆烙痕检测区域"), 137 | match=re.compile("支援记忆烙痕")) 138 | zhiyuan = boxes[0] 139 | self.click_box(zhiyuan, relative_y=-0.5) 140 | self.sleep(2) 141 | assist_laohen_type = self.config.get("支援烙痕类型") 142 | laohen_type_index = find_index(assist_laohen_type, self.stats_seq) 143 | gap = self.height_of_screen((1564 - 1209) / 4 / 1080) 144 | laohen_type_y = self.height_of_screen(68 / 1080) 145 | laohen_type_x = self.screen_width - self.height_of_screen((1920 - 1564) / 1080) - ( 146 | 4 - laohen_type_index) * gap 147 | self.click(laohen_type_x, laohen_type_y) 148 | assist = self.wait_until(self.choose_assist_laohen_check, 149 | time_out=300, 150 | wait_until_before_delay=3, 151 | post_action=lambda: self.click_relative(0.91, 0.07)) 152 | self.click_box(assist) 153 | self.sleep(2) 154 | select = self.ocr(box=self.box_of_screen(0.5, 0.5, width=0.5, height=0.5, name="支援记忆烙痕检测区域"), 155 | match=re.compile("选择支援记忆烙痕")) 156 | if select: 157 | self.log_info("选了上次选中的支援烙痕, 再选一遍") 158 | self.choose_assist_laohen() 159 | 160 | def choose_assist_laohen_check(self): 161 | manpos = self.find_feature('manpo_90', 1, 1, 0.90) 162 | if manpos: 163 | boxes = self.ocr(box=self.box_of_screen(0.1, 0.3, width=0.9, height=0.6, name="支援烙痕检测区域")) 164 | for box_90 in manpos: 165 | laohen = box_90.find_closest_box('all', boxes, 166 | lambda box: self.config.get('支援烙痕') in box.name) 167 | if laohen and box_90.closest_distance(laohen) < box_90.width * 2: 168 | self.log_info(f"查找到满破烙痕 {laohen} {box_90}") 169 | return box_90 170 | 171 | def enter_manxun(self): 172 | if not self.route: 173 | self.main_go_to_manxun() 174 | self.wait_click_box( 175 | lambda: self.ocr(box=self.box_of_screen(0.2, 0.5, width=0.8, height=0.3, name="漫巡路线检测区域"), 176 | match=self.config['路线'])) 177 | else: 178 | self.click_box(self.route) 179 | go_manxun = self.wait_click_box( 180 | lambda: self.ocr(box=self.right_button_zone, match='前往回廊漫巡')) 181 | self.sleep(1) 182 | continue_manxun = self.ocr(box=self.dialog_zone, match='继续漫巡') 183 | if continue_manxun: 184 | self.click_box(continue_manxun) 185 | self.wait_click_box( 186 | lambda: self.ocr(box=self.dialog_zone, match='确认')) 187 | return True 188 | else: 189 | self.log_debug('start_manxun') 190 | # start_manxun = self.wait_click_box( 191 | # lambda: self.ocr(box=self.star_combat_zone, match='开始新漫巡'), time_out=4) 192 | # self.sleep(1) 193 | boxes = self.ocr(box=self.box_of_screen(0.2, 0.5, width=0.3, height=0.3, name="精神改善剂检测区域"), 194 | match=re.compile(r'^可回复精神力')) 195 | if len(boxes) == 1: 196 | self.click_box(boxes[0], relative_y=-4) 197 | self.wait_click_box( 198 | lambda: self.ocr(box=self.right_button_zone, match='确认使用')) 199 | self.sleep(0.5) 200 | self.click_relative(0.95, 0.5) 201 | self.sleep(3) 202 | self.click_box(go_manxun) 203 | return False 204 | 205 | @property 206 | def right_button_zone(self): 207 | return self.box_of_screen(0.5, 0.6, width=0.5, height=0.3, name="按钮检测区域") 208 | 209 | @property 210 | def top_right_button_zone(self): 211 | return self.box_of_screen(0.5, 0, width=0.5, height=0.2, name="右上按钮检测区域") 212 | 213 | @property 214 | def bottom_button_zone(self): 215 | return self.box_of_screen(0.2, 0.8, width=0.6, height=0.2, name="下面按钮检测区域") 216 | -------------------------------------------------------------------------------- /task/DailyTask.py: -------------------------------------------------------------------------------- 1 | import random 2 | import re 3 | 4 | from ok.color.Color import calculate_color_percentage 5 | from task.BJTask import BJTask 6 | 7 | 8 | class DailyTask(BJTask): 9 | 10 | def __init__(self): 11 | super().__init__() 12 | self.route = None 13 | self.name = "一键收菜清日常(最好用模拟器)" 14 | self.description = "打开模拟器,启动游戏,签到,进入主页, 刷日常" 15 | self.default_config = {"免费购物": True, "刷体力总开关": True, "随机刷材料次数": 3, 16 | "随机刷技能书次数": 3, "喝茶3次": True, "友情点": True, 17 | "收菜": True, "免费召唤烙痕": True, "升级烙痕": True, "领任务奖励": True 18 | } 19 | self.config_description = { 20 | "随机刷技能书次数": "每次40体", 21 | "随机刷材料次数": "每次40体" 22 | } 23 | self.info['使用体力药'] = 0 24 | 25 | def run(self): 26 | if self.ensure_main_page(): 27 | self.log_info("进入游戏主页成功!", True) 28 | else: 29 | self.log_error("进入游戏主页失败!", notify=True) 30 | return 31 | if self.config.get("刷体力总开关") and ( 32 | self.config.get('随机刷材料次数') > 0 or self.config.get('随机刷技能书次数') > 0): 33 | self.combat() 34 | if self.config.get("喝茶3次"): 35 | self.hecha() 36 | if self.config.get("友情点"): 37 | self.friends() 38 | if self.config.get("收菜"): 39 | self.shoucai() 40 | if self.config.get("免费购物"): 41 | self.go_shopping() 42 | if self.config.get("免费召唤烙痕"): 43 | self.free_summon() 44 | if self.config.get("升级烙痕"): 45 | self.laohen_up() 46 | if self.config.get("领任务奖励"): 47 | self.claim_quest() 48 | self.claim_dayueka() 49 | self.claim_guild() 50 | self.log_info("收菜完成!", True) 51 | 52 | def claim_guild(self): 53 | self.click_relative(0.19, 0.04) 54 | self.click_to_continue_wait(time_out=3) 55 | self.click_relative(0.5, 0.06) 56 | self.sleep(1) 57 | self.click_relative(0.56, 0.37) 58 | self.wait_click_ocr(0.70, 0.28, 0.81, 0.36, match="领取奖励", time_out=3) 59 | self.sleep(1) 60 | self.click_to_continue_wait(time_out=3) 61 | self.sleep(1) 62 | self.click_relative(0.56, 0.06) 63 | self.sleep(1) 64 | self.go_home_wait() 65 | 66 | def claim_dayueka(self): 67 | self.choose_main_menu("活动中心") 68 | if self.wait_click_ocr(0.02, 0.09, 0.16, 0.91, match='叶脉联结'): 69 | if self.wait_click_ocr(0.35, 0.80, 0.44, 0.85, match="任务总览", time_out=4): 70 | if self.wait_click_ocr(0.68, 0.80, 0.77, 0.85, match="全部领取", time_out=3): 71 | self.wait_until(lambda: self.ocr(0.35, 0.80, 0.44, 0.85, match="任务总览"), 72 | post_action=lambda: self.click_relative(0.5, 0.9)) 73 | self.wait_click_ocr(0.2, 0.4, 0.27, 0.44, match="本周任务") 74 | if self.wait_click_ocr(0.68, 0.80, 0.77, 0.85, match="全部领取", time_out=3): 75 | self.wait_until(lambda: self.ocr(0.35, 0.80, 0.44, 0.85, match="任务总览"), 76 | post_action=lambda: self.click_relative(0.5, 0.9)) 77 | self.go_home_wait() 78 | 79 | def claim_quest(self): 80 | self.wait_click_ocr(box=self.main_menu_zone, match=re.compile(r"任务|完成")) 81 | self.wait_click_ocr(0.03, 0.2, 0.16, 0.83, match="日常") 82 | if self.wait_click_ocr(0.8, 0.75, 0.93, 0.81, match=re.compile(r"领取"), time_out=4): 83 | self.click_to_continue_wait(time_out=6) 84 | self.wait_click_ocr(0.03, 0.2, 0.16, 0.83, match="周常") 85 | if self.wait_click_ocr(0.8, 0.75, 0.93, 0.81, match=re.compile(r"领取"), time_out=4): 86 | self.click_to_continue_wait(time_out=6) 87 | self.wait_until(self.check_is_main, post_action=lambda: self.click_relative(0.37, 0.05), time_out=10) 88 | 89 | def laohen_up(self): 90 | self.choose_main_menu("记忆烙痕") 91 | self.wait_click_ocr(.86, 0.02, 0.95, 0.07, match="排序与筛选") 92 | boxes = self.wait_ocr(0.3, 0.3, 0.73, 0.77, match=["等级", "特质等级", "确认设置"]) 93 | self.click_box(boxes[1]) 94 | self.sleep(0.5) 95 | self.click_box(boxes[0]) 96 | self.sleep(0.5) 97 | self.click_box(boxes[0]) 98 | self.sleep(0.5) 99 | self.click_box(boxes[2]) 100 | self.sleep(1) 101 | self.click_relative(0.71, 0.35) 102 | self.wait_click_ocr(0.72, 0.85, 0.89) 103 | self.sleep(1) 104 | self.click_relative(0.74, 0.62) 105 | self.sleep(1) 106 | self.click_relative(0.62, 0.79) 107 | self.sleep(3) 108 | self.click_relative(0.5, 0.95) 109 | self.sleep(3) 110 | self.click_relative(0.5, 0.95) 111 | self.go_home_wait() 112 | 113 | def hecha(self): 114 | self.go_into_menu("午后茶憩", confirm=True) 115 | for i in range(3): 116 | self.wait_ocr(0.84, 0.56, 0.95, 0.70, match="台球", time_out=30) 117 | self.click_relative(0.9, 0.84) 118 | moqi = self.wait_ocr(0.46, 0.26, 0.54, 0.31, match="默契值", time_out=4) 119 | if moqi: 120 | if i == 0: 121 | self.click_box(moqi) 122 | self.sleep(1) 123 | self.click_box(moqi) 124 | self.sleep(1) 125 | self.click_relative(0.4, 0.4) 126 | self.sleep(1) 127 | self.click_relative(0.82, 0.78) 128 | 129 | self.heyici() 130 | self.go_back_confirm() 131 | 132 | def heyici(self): 133 | diandan = self.wait_until( 134 | condition=lambda: self.ocr(0.8, 0.83, 0.95, 0.95, match="再来一杯") or 135 | self.ocr(0.8, 0.7, 0.95, 0.83, match="开始点单") or 136 | self.find_feature("hecha_chat", 137 | x=0.65, y=0.31, 138 | to_x=0.93, 139 | to_y=0.77, 140 | threshold=0.8), 141 | post_action=lambda: self.click_relative(0.5, 0.95), time_out=60) 142 | if not diandan: 143 | self.log_error("找不到开始点单") 144 | raise Exception("找不到开始点单") 145 | 146 | if diandan[0].name == '再来一杯': # 如果是喝完直接跳过 147 | self.click_box(diandan) 148 | elif diandan[0].name == '开始点单': # 如果是喝完直接跳过 149 | self.click_box(diandan) 150 | while True: 151 | next_step = self.wait_ocr(.76, .74, .9, .85, match=["下一步", "开始制作"], time_out=3) 152 | if not next_step: 153 | break 154 | boxes = self.ocr(0.64, 0.23, .9, .74) 155 | for box in boxes: 156 | self.click_box(box) 157 | self.sleep(0.2) 158 | self.click_box(next_step) 159 | self.wait_until(self.check_hewan, time_out=120) 160 | 161 | def check_hewan(self): 162 | end = self.ocr(.86, .7, .95, .81, match="结束茶憩") 163 | choices = self.find_feature("hecha_chat", x=0.65, y=0.31, to_x=0.93, to_y=0.77, threshold=0.8) 164 | if end: 165 | self.click_box(end) 166 | self.sleep(2) 167 | self.click_relative(.96, 0.5) 168 | return True 169 | elif choices: 170 | choice = random.choice(choices) 171 | self.click_box(choice) 172 | else: 173 | self.click_relative(0.91, 0.91) 174 | 175 | def combat(self): 176 | self.choose_main_menu("外勤作战") 177 | # self.wait_click_ocr(.07, .9, .82, match="物资筹备") 178 | combats = self.find_combats() 179 | 180 | if self.config.get('随机刷材料次数') > 0: 181 | self.click_box(combats[0]) 182 | self.dingxiang_combat() 183 | if self.config.get('随机刷技能书次数') > 0: 184 | combats = self.find_combats() 185 | self.click_box(combats[1]) 186 | self.guangke_combat() 187 | self.sleep(1) 188 | self.go_home_wait() 189 | 190 | def find_combats(self): 191 | return self.wait_ocr(x=0.07, y=0.8, to_x=0.96, to_y=0.94, match=["定向物资保障", "光刻协议"]) 192 | 193 | def guangke_combat(self): 194 | target_list = self.wait_ocr(x=0.03, y=0.36, to_y=0.63, match=re.compile(r"协议")) 195 | 196 | self.click_box(random.choice(target_list)) 197 | self.fast_combat(self.config.get("随机刷技能书次数")) 198 | 199 | def dingxiang_combat(self): 200 | sub_list = self.wait_until(lambda: self.ocr(x=0.1, y=0.25, to_x=0.9, to_y=0.4, match="百炼成钢")) 201 | sub_list.append(self.ocr(x=0.1, y=0.25, to_x=0.9, to_y=0.4, match="森严壁垒")) 202 | main_list = self.ocr(x=0.6, y=0.25, to_x=0.9, to_y=0.8, match=["人造机械", "矿石材料", "结晶宝石"]) 203 | 204 | self.click_box(random.choice(main_list)) 205 | self.sleep(0.2) 206 | self.click_box(random.choice(sub_list)) 207 | self.fast_combat(self.config.get("随机刷材料次数")) 208 | self.wait_click_ocr(to_x=0.2, to_y=0.1, match="返回") 209 | self.sleep(2) 210 | 211 | def fast_combat(self, target_count): 212 | while True: 213 | self.wait_click_ocr(0.7, 0.8, to_x=0.9, match="快速战斗") 214 | yao = self.wait_ocr(0.2, 0.5, 0.8, 0.8, match=re.compile(r"^可回复行动力"), time_out=4) 215 | if yao: 216 | self.eat_yao(yao) 217 | else: 218 | break 219 | count = 1 220 | while count < target_count: 221 | boxes = self.wait_ocr(0.5, 0.2, 0.7, 0.45, match=["补充", "+"]) 222 | plus = self.box_of_screen(923 / 1600, 325 / 900, 977 / 1600, 358 / 900) 223 | add_stam = boxes[0] 224 | black_percent = calculate_color_percentage(self.frame, plus_button_black_color, plus) 225 | self.log_debug(f'检查体力药加号 black_percent: {black_percent} {count}') 226 | if black_percent > 0.1: 227 | self.click_box(plus) 228 | self.sleep(1) 229 | count += 1 230 | else: # 需要吃药 231 | self.log_info(f"吃体力药 {count}") 232 | self.click_box(add_stam) 233 | yao = self.wait_ocr(0.2, 0.5, 0.8, 0.8, match=re.compile(r"^可回复行动力"), time_out=4) 234 | if not yao: 235 | raise ValueError("找不到体力药") 236 | self.eat_yao(yao) 237 | 238 | self.wait_click_ocr(0.5, 0.7, to_x=0.7, to_y=0.9, match="快速战斗") 239 | self.sleep(3) 240 | self.click_relative(0.9, 0.5) 241 | self.sleep(1) 242 | self.click_relative(0.9, 0.5) 243 | 244 | def eat_yao(self, yao): 245 | self.info["使用体力药"] = self.info.get("使用体力药", 0) + 1 246 | if self.info["使用体力药"] > 10: 247 | raise Exception("吃了太多体力药!脚本可能出错") 248 | self.click_box(yao) 249 | self.wait_confirm() 250 | self.sleep(1) 251 | self.click_relative(0.9, 0.5) 252 | self.sleep(1) 253 | 254 | # def wait_combat 255 | # complete = self.ocr 256 | 257 | def friends(self): 258 | self.sleep(2) 259 | self.click_relative(225 / 1600, 43 / 900) 260 | self.wait_click_ocr(0.08, 0.10, 0.20, 0.21, match="好友列表") 261 | self.wait_click_ocr(0.8, to_y=0.15, match=re.compile(".*赠送$")) 262 | self.click_to_continue_wait(time_out=3) 263 | self.go_home_wait() 264 | 265 | def go_shopping(self): 266 | self.choose_main_menu("有猫零售") 267 | self.wait_click_box(lambda: self.ocr(box=self.box_of_screen(0, 0.1, to_x=0.3), match="精选礼包")) 268 | self.sleep(1) 269 | self.wait_click_ocr(0.20, 0.13, 0.61, 0.24, match='有猫精选') 270 | free = self.ocr(box=self.box_of_screen(0.1, 0.5, to_x=0.5), match="免费") 271 | if free: 272 | self.click_box(free) 273 | self.log_info("点击购买免费礼包") 274 | self.wait_click_box(lambda: self.ocr(box=self.box_of_screen(0.5, 0.5, to_x=0.8), match=re.compile("购"))) 275 | self.click_to_continue_wait() 276 | else: 277 | self.log_info("已经买过免费礼包") 278 | self.go_home_wait() 279 | 280 | def click_last_summon(self): 281 | laohens = self.ocr(box=self.box_of_screen(0, 0.47, to_x=0.3), match="记忆烙痕") 282 | if len(laohens) > 0: 283 | self.click_box(laohens[-1]) 284 | return True 285 | 286 | def free_summon(self): 287 | self.choose_main_menu("精神深潜") 288 | self.wait_until(self.click_last_summon) 289 | if self.wait_click_ocr(0.5, 0.8, match="无消耗", time_out=4): 290 | self.log_info("开始免费召唤") 291 | self.wait_click_ocr(0.9, 0, to_y=0.2, match="SKIP") 292 | self.wait_click_ocr(0.4, 0.8, to_x=0.6, match=re.compile(r"^确")) 293 | self.click_to_continue_wait() 294 | else: 295 | self.log_info("免费召唤次数没了") 296 | self.go_home_wait() 297 | 298 | def shoucai(self): 299 | self.go_into_menu("白荆穹顶", True) 300 | self.wait_click_ocr(0.9, 0.9, match="全部收取", time_out=40) 301 | self.click_to_continue_wait() 302 | self.go_back_confirm() 303 | 304 | def go_back_confirm(self): 305 | self.wait_click_ocr(to_x=0.2, to_y=0.1, match="返回") 306 | self.wait_confirm() 307 | self.wait_main() 308 | 309 | 310 | plus_button_black_color = { 311 | 'r': (40, 70), # Red range 312 | 'g': (40, 70), # Green range 313 | 'b': (40, 70) # Blue range 314 | } 315 | -------------------------------------------------------------------------------- /task/NewManXunTask.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | 4 | from typing_extensions import override 5 | 6 | from ok.color.Color import calculate_color_percentage 7 | from ok.feature.Box import find_box_by_name, find_boxes_by_name, find_boxes_within_boundary, average_width 8 | from ok.task.TaskExecutor import FinishedException, TaskDisabledException 9 | from task.BJTask import BJTask 10 | 11 | 12 | def get_current_stats(s): 13 | try: 14 | # Split the string on '/' 15 | parts = s.split('/') 16 | # Check if there are exactly two parts and both are digits 17 | if len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit(): 18 | return int(parts[0]) 19 | else: 20 | return None 21 | except Exception as e: 22 | print(f"An error occurred: {e}") 23 | return None 24 | 25 | 26 | class NewManXunTask(BJTask): 27 | 28 | def __init__(self): 29 | super().__init__() 30 | self.update_stats_queue = None 31 | self.name = "执行一次自动漫巡" 32 | self.description = """自动漫巡, 必须进入漫巡后开始, 并开启追踪 33 | """ 34 | self.click_no_brainer = ["直接胜利", "属性提升", re.compile(r"^前进"), "通过", "继续", "收下", "跳过", 35 | "开始强化", 36 | re.compile(r"^解锁技能:"), re.compile(r"^精神负荷降低"), "漫巡推进"] 37 | self.default_config = { 38 | "深度等级最多提升到": 12, 39 | "低深度选项优先级": ["属性平台", "技能平台"], 40 | "高深度选项优先级": ["技能平台", "属性平台"], 41 | "高低深度分界": 6, 42 | "属性优先级": ["专精", "攻击", "终端", "防御", "体质", "技能点"], 43 | # "终端900上限": False, 44 | "烙痕唤醒黑名单": ["幕影重重", "谎言之下", "馆中遗影"], 45 | # "跳过战斗": ["鱼叉将军-日光浅滩E"], 46 | # "烙痕属性提升不选技能点": True, 47 | "自定义路径": ["3-01|[技能平台,属性平台]|2", "3-02|[技能平台,属性平台]|2", "3-04|[技能平台,属性平台]|1"] 48 | } 49 | self.config_description = { 50 | "投降跳过战斗": "如果无法直接胜利, 自动投降跳过", 51 | "深度等级最多提升到": "海底图建议12", 52 | "高低深度分界": "比如可以配置低深度优先记忆强化, 高深度优先高维同调", 53 | "烙痕唤醒黑名单": "可以使用烙痕名称或者核心技能名称, 部分匹配即可", 54 | "跳过战斗": "不打的战斗, 比如鱼叉将军", 55 | "烙痕唤醒属性优先级": "比如用烙痕凑900终端", 56 | "自定义路径": "使用半角符号,格式为 区域|[选项1名称,选项2名称]|选第几个。" 57 | } 58 | self.stats_up_re = re.compile(r"([\u4e00-\u9fff]+)\+(\d+)(?:~(\d+))?") 59 | self.pause_combat_message = "未开启自动战斗, 无法继续漫巡, 暂停中, 请手动完成战斗或开启自动跳过后继续" 60 | self.stats_seq = ["体质", "防御", "攻击", "专精", "终端"] 61 | self.update_stats_thread = None 62 | self.ocr_target_height = 700 # 缩小图片提升ocr速度 63 | self.total_anjiao_count = 3 # 预测暗礁属性 64 | self.custom_routes = [] 65 | self.to_update_stats = True 66 | self.ocr_count = 0 67 | self.ocr_time = 0 68 | 69 | def on_create(self): 70 | self.log_debug('on_create') 71 | 72 | @property 73 | def zhongduan_max(self): 74 | return 900 if self.config.get("终端900上限") else 1250 75 | 76 | def validate_config(self, key, value): 77 | self.custom_routes.clear() 78 | if key == '自定义路径': 79 | for v in value: 80 | try: 81 | zone, choices, to_choose = v.split('|') 82 | list_of_choices = choices[1:-1].split(',') 83 | config_list = [zone, list_of_choices, int(to_choose)] 84 | if len(config_list) != 3: 85 | return f'自定义路径配置格式错误:{v}' 86 | self.custom_routes.append(config_list) 87 | except Exception as e: 88 | self.log_error(f'自定义路径 配置错误:{v}', exception=e) 89 | return f'自定义路径配置格式错误:{v}' 90 | self.log_info(f'加载自定义路径:{self.custom_routes}') 91 | 92 | def end(self, message, result=False): 93 | self.log_info(f"执行结束:{message}") 94 | return result 95 | 96 | @property 97 | def choice_zone(self): 98 | return self.box_of_screen(0.74, 0.50, 0.91, 0.83, name="选项检测区域") 99 | 100 | @property 101 | def current_zone(self): 102 | return self.box_of_screen(0.6, 0.8, width=0.3, height=0.2, name="当前区域") 103 | 104 | @property 105 | def stats_zone(self): 106 | return self.box_of_screen(0.28, 0.92, 0.73, 0.98, name="当前属性区域") 107 | 108 | @property 109 | def dialog_zone(self): 110 | return self.box_of_screen(0.05, 0.24, 0.92, 0.87, name="弹窗检测区域") 111 | 112 | @property 113 | def battle_popup_zone(self): 114 | return self.box_of_screen(0.25, 0.15, width=0.5, height=0.85, name="战斗检测区域") 115 | 116 | def filter_gaowei_number_zone(self, boxes): 117 | return self.box_of_screen(0.25, 0.5, width=0.5, height=0.5).in_boundary(boxes) 118 | 119 | @override 120 | def run(self): 121 | self.log_info(f'run, config {self.config}') 122 | if not self.check_is_manxun_ui(): 123 | self.log_error("必须选好角色进入漫巡界面后开始, 并且开启路线追踪", notify=True) 124 | self.screenshot("必须选好角色进入漫巡界面后开始") 125 | return 126 | while True: 127 | try: 128 | self.loop() 129 | except FinishedException: 130 | self.log_info("自动漫巡任务结束", notify=True) 131 | return 132 | except TaskDisabledException: 133 | self.log_info("点击停止", notify=True) 134 | return 135 | except Exception as e: 136 | self.screenshot(f"运行异常{e}") 137 | self.log_error(f"运行异常,已暂停:", e, True) 138 | self.pause() 139 | 140 | def check_is_manxun_ui(self): 141 | if self.ocr_zone(): 142 | return True 143 | try: 144 | return self.do_handle_dialog(-1) 145 | except Exception as e: 146 | self.log_error(f'check_is_manxun_ui handle dialog failed', e) 147 | 148 | def loop(self, choice=-1): 149 | choices, choice_clicked = self.click_choice(choice) 150 | if not choice_clicked: 151 | self.logger.error(f"没有选项可以点击, 请确认是否开启漫巡设置全部简化!") 152 | self.screenshot("没有选项可以点击") 153 | return self.handle_dialog_with_retry(choice) 154 | self.wait_until(lambda: self.handle_dialog_with_retry(choice), wait_until_before_delay=1.5) 155 | 156 | def handle_dialog_with_retry(self, choice): 157 | for i in range(2): 158 | try: 159 | self.do_handle_dialog(choice) 160 | self.sleep(0.5) 161 | return True 162 | except FinishedException as e: 163 | raise e 164 | except Exception as e: 165 | if i == 0: 166 | self.screenshot("处理对话框异常重试一次") 167 | self.log_error("处理对话框异常 重试一次", e) 168 | self.sleep(5) 169 | continue 170 | else: 171 | raise e 172 | 173 | def stats_priority(self, gaowei): 174 | return self.config['属性优先级'] 175 | 176 | def update_current_stats(self): 177 | if self.to_update_stats: 178 | self.handler.post(lambda: self.do_update_current_stats(self.frame)) 179 | 180 | def do_update_current_stats(self, frame): 181 | # if frame is None: 182 | # self.log_info("No frame in queue, destroyed") 183 | # return 184 | # self.ocr_stats(frame) 185 | pass 186 | 187 | def ocr_stats(self, frame=None): 188 | boxes = self.ocr(box=self.stats_zone, match=re.compile(r'^[1-9]\d*$'), frame=frame) 189 | if len(boxes) != 5: 190 | # self.log_error(f"无法找到5个属性, {boxes}") 191 | return False 192 | else: 193 | stats = [int(box.name) for box in boxes] 194 | self.update_stats_for_anjiao(stats) 195 | self.info['当前属性'] = stats 196 | self.log_debug(f"ocr_stats {stats}") 197 | return True 198 | 199 | def update_stats_for_anjiao(self, stats): 200 | missing_count = self.total_anjiao_count - self.info.get('暗礁次数', 0) 201 | if missing_count > 2: 202 | missing_count = 2 203 | for _ in range(missing_count): 204 | sorted_indexes = target_index_array(stats) 205 | for i, sort in enumerate(sorted_indexes): 206 | if sort < 2: # 最低两个加75 207 | stats[i] += 75 208 | elif sort < 3: 209 | stats[i] += 40 210 | else: 211 | stats[i] += 10 212 | 213 | def do_handle_dialog(self, choice): 214 | start = time.time() 215 | boxes = self.ocr(box=self.dialog_zone) 216 | cost = time.time() - start 217 | self.ocr_time += cost 218 | self.ocr_count += 1 219 | self.info['OCR Time'] = f'{(self.ocr_time / self.ocr_count):.3f}' 220 | choices = self.do_find_choices() 221 | if choices is not True and len(choices) > 0: 222 | self.log_info(f"没有弹窗, 进行下一步") 223 | return True 224 | self.logger.debug(f"检测对话框区域 {boxes}") 225 | ups = find_boxes_by_name(boxes, "提升") 226 | if huanxing := find_boxes_by_name(boxes, '烙痕唤醒'): 227 | self.try_handle_laohen_choices(huanxing, boxes) 228 | elif depth_ups := find_boxes_by_name(boxes, ["维持当前深度", "提升深度到", "降低深度到"]): 229 | levels = [] 230 | for depth in depth_ups: 231 | levels.append(int(depth.find_closest_box('right', boxes).name)) 232 | self.log_debug(f'depth levels {levels}') 233 | for reverse_index, value in enumerate(reversed(levels)): 234 | original_index = len(levels) - 1 - reverse_index 235 | if value <= self.config.get('深度等级最多提升到', 12): 236 | self.click_box(depth_ups[original_index]) 237 | self.log_info(f'点击等级提升 {value} {depth_ups[original_index]}') 238 | self.info['漫巡深度'] = value 239 | break 240 | self.click_box(depth_ups[0]) 241 | elif confirm := find_box_by_name(boxes, "完成漫巡"): 242 | self.click_box(confirm) 243 | self.wait_click_box(lambda: self.ocr(box=self.dialog_zone, match="确认")) 244 | self.wait_click_box(lambda: self.ocr(box=self.star_combat_zone, match="跳过漫巡回顾")) 245 | self.wait_click_box(lambda: self.ocr(box=self.star_combat_zone, match="点击屏幕确认结算")) 246 | if self.confirm_generate(): 247 | self.wait_click_box( 248 | lambda: self.ocr(box=self.box_of_screen(0.7, 0.6, width=0.3, height=0.4), match="确认生成")) 249 | self.wait_click_box(lambda: self.ocr(box=self.dialog_zone, match="完成漫巡")) 250 | self.wait_click_box( 251 | lambda: self.ocr(box=self.box_of_screen(0.5, 0.5, width=0.4, height=0.3), match="确定完成")) 252 | self.wait_until(lambda: self.ocr(box=self.box_of_screen(0, 0, width=0.2, height=0.2)), 253 | pre_action=lambda: self.click_relative(0.5, 0.1), time_out=90) 254 | raise FinishedException() 255 | elif len(ups) > 1: 256 | stats = [] 257 | for up in ups: 258 | stats.append(remove_non_digits_and_convert(up.find_closest_box('up', boxes).name)) 259 | self.log_info(f'高位同调选项: {stats}') 260 | max_value = max(stats) 261 | max_index = stats.index(max_value) 262 | self.click_box(ups[max_index]) 263 | self.log_info(f"高位同调 点击最高 {ups[max_index]} {stats[max_index]}") 264 | self.to_update_stats = True 265 | elif weichi_huibi := find_box_by_name(boxes, re.compile('^维持深度回避')): 266 | self.log_info('维持深度回避') 267 | self.click_box(weichi_huibi) 268 | elif kaizhan := find_box_by_name(boxes, re.compile('^开战')): 269 | if avoid := find_box_by_name('维持当前深度回避', boxes): 270 | self.click_box(avoid) 271 | self.log_info('wait 维持当前深度 ocr done') 272 | else: 273 | self.log_info('点击开战') 274 | self.click_box(kaizhan) 275 | self.wait_ocr(box=self.dialog_zone, match=re.compile("深度"), time_out=60) 276 | self.do_handle_dialog(choice) 277 | elif confirm := find_boxes_by_name(boxes, "技能获取"): 278 | self.handle_skill_dialog(boxes, confirm) 279 | elif find_box_by_name(boxes, "获得了一些技能点"): 280 | self.log_info(f"获取技能点成功") 281 | self.click_box(find_box_by_name(boxes, re.compile(r"^\+\d+"))) 282 | elif keyin := find_box_by_name(boxes, "刻印技能上限"): 283 | keyin.y += keyin.height * 2.5 284 | keyin.x = self.width / 2 - keyin.width / 2 285 | self.draw_boxes("confirm_by_offset", keyin) 286 | self.log_info(f"区域技能,点击两次") 287 | self.click_box(keyin) 288 | self.sleep(0.5) 289 | self.click_box(keyin) 290 | elif start_combat := find_box_by_name(boxes, "开始战斗"): 291 | self.click_box(start_combat) 292 | self.logger.debug( 293 | f"开始战斗 自动战斗") 294 | self.auto_combat() 295 | elif no_brain_boxes := find_boxes_by_name(boxes, self.click_no_brainer): 296 | no_brain_box = no_brain_boxes[-1] 297 | self.click_box(no_brain_box) 298 | if no_brain_box.name == '属性提升': 299 | self.log_info('暗礁属性提升') 300 | self.info_add('暗礁次数') 301 | self.to_update_stats = True 302 | elif no_brain_box.name == '漫巡推进' or no_brain_box.name == '前进': 303 | self.sleep(4) 304 | self.ocr_zone() 305 | else: 306 | self.log_info(f"点击固定对话框: {no_brain_box.name}") 307 | elif stats_up_choices := self.find_stats_up(boxes): 308 | self.handle_stats_up(stats_up_choices) 309 | self.to_update_stats = True 310 | else: 311 | raise Exception(f"未知弹窗 无法处理") 312 | return True 313 | 314 | def ocr_zone(self): 315 | zone = self.ocr(box=self.box_of_screen(0.09, 0.11, 0.18, 0.15)) 316 | self.log_debug(f'ocr zone {zone}') 317 | if zone: 318 | if '浮世' not in zone[0].name: 319 | return False 320 | self.info['当前区域'] = zone[0].name 321 | self.log_info(f'当前区域 {self.info["当前区域"]}') 322 | return self.info['当前区域'] 323 | 324 | def confirm_generate(self): 325 | return False 326 | 327 | def find_stats_up(self, boxes): 328 | for box in boxes: 329 | if re.search(r"^[\u4e00-\u9fa5]{2}$", box.name): 330 | closest = box.find_closest_box("right", boxes) 331 | if closest is not None: 332 | distance = closest.closest_distance(box) 333 | if distance < box.width: 334 | match = re.search(r"\+(\d+)(?:~(\d+))?", closest.name) 335 | if match: 336 | box.name += match.group(0) 337 | self.logger.debug(f"合并较近属性和数值, {box} {closest.name}") 338 | closest.name = "" 339 | 340 | return find_boxes_by_name(boxes, self.stats_up_re) 341 | 342 | def auto_combat(self): 343 | # if not self.wait_click_ocr(0.73, 0.85, 1, 1, match="作战开始", time_out=5, raise_if_not_found=False): 344 | self.wait_click_ocr(0.7, 0, 0.82, 0.1, match="上次编队", time_out=5, raise_if_not_found=False) 345 | self.sleep(1) 346 | self.log_info('开始查找上次编队') 347 | self.wait_click_ocr(0.73, 0.85, 1, 1, match="保存更改", time_out=10, raise_if_not_found=False) 348 | self.sleep(1) 349 | self.wait_click_ocr(0.73, 0.85, 1, 1, match="作战开始", time_out=10, raise_if_not_found=False) 350 | self.log_info('开始查找开始战斗') 351 | start_combat = self.wait_ocr(box=self.star_combat_zone, match="开始战斗", time_out=180, raise_if_not_found=True) 352 | self.sleep(2) 353 | self.log_info('点击开始战斗') 354 | self.click_box(start_combat) 355 | self.wait_until(self.ocr_zone, time_out=300) 356 | 357 | def handle_stats_up(self, stats_up_choices): 358 | stats_up_parsed = self.parse_stats_choices(stats_up_choices) 359 | skilled_list = [] 360 | target = None 361 | priority = self.stats_priority(False) 362 | for stat in priority: 363 | if target is not None: 364 | break 365 | for box_stat, _, box in stats_up_parsed: 366 | if box_stat == stat: 367 | target = box 368 | break 369 | if target is None: 370 | if skilled_list: 371 | target = skilled_list[0] 372 | else: 373 | target = stats_up_parsed[0][2] 374 | self.log_info( 375 | f"选择升级属性 {stats_up_choices} stats_up_parsed:{stats_up_parsed} target:{target} priority:{priority} skilled_list:{skilled_list}") 376 | if target: 377 | self.click_box(target) 378 | return True 379 | 380 | def handle_skill_dialog(self, boxes, confirm): 381 | confirm = confirm[-1] 382 | search_skill_name_box = confirm.copy(-confirm.width / 2, -confirm.height * 2, confirm.width, 383 | confirm.height * 0.7) 384 | self.draw_boxes("skill_search_area", search_skill_name_box) 385 | skills = find_boxes_within_boundary(boxes, search_skill_name_box) 386 | self.draw_boxes("skills", skills) 387 | self.info_add_to_list("获得技能", [obj.name for obj in skills]) 388 | self.check_skills() 389 | self.log_info(f"获取技能 {skills}") 390 | self.click_box(confirm) 391 | self.sleep(4) 392 | self.ocr_zone() 393 | 394 | def check_skills(self): 395 | pass 396 | 397 | def check_custom_route(self, choices): 398 | for custom_route in self.custom_routes: 399 | zone, custom_choices, custom_index = custom_route 400 | zone_match = (zone in self.info.get('当前区域', [])) 401 | size_custom = len(custom_choices) 402 | self.log_debug( 403 | f'check_custom_route {choices} 当前区域:{self.info.get("当前区域")} {zone_match} {size_custom}') 404 | if zone_match: 405 | if size_custom == len(choices): 406 | all_match = True 407 | for i in range(size_custom): 408 | if custom_choices[i] not in choices[i].name: 409 | all_match = False 410 | break 411 | if all_match: 412 | index = custom_index - 1 413 | self.log_info(f"自定义路径找到,{custom_route} 点击第{custom_index}个 {choices[index]}") 414 | self.click_box(choices[index]) 415 | return True, choices[index] 416 | return False, None 417 | 418 | def click_choice(self, index=-1): 419 | choices = self.find_choices() 420 | if choices is True: 421 | self.log_debug(f'choices in bg try handle dialog again') 422 | return None, None 423 | if choices is None: 424 | return None, None 425 | if abs(index) > len(choices): 426 | raise ValueError(f"click_choice out of bonds") 427 | else: 428 | handled, clicked = self.check_custom_route(choices) 429 | if handled: 430 | return choices, clicked 431 | else: 432 | priority = self.config['低深度选项优先级'] if self.info.get('漫巡深度', 0) < self.config[ 433 | '高低深度分界'] else \ 434 | self.config[ 435 | '高深度选项优先级'] 436 | index = find_priority_string(choices, priority, index) 437 | self.update_current_stats() 438 | self.click_box(choices[index]) 439 | self.log_info( 440 | f"点击选项:{choices[index]}, 使用优先级 {priority}, index {index}, {choices}") 441 | return choices, choices[index] 442 | 443 | def try_handle_laohen_choices(self, huanxing, boxes): 444 | stats_up_choices = self.find_stats_up(boxes) 445 | if stats_up_choices and self.handle_stats_up(stats_up_choices): 446 | return 447 | target = huanxing[0] 448 | if len(huanxing) == 2: 449 | black_list = [re.compile(s) for s in self.config["烙痕唤醒黑名单"]] + [re.compile("核心技能已解锁满级")] 450 | blacks = find_boxes_by_name(boxes, black_list) 451 | for black in blacks: 452 | if black.x < self.width_of_screen(0.5): 453 | target = huanxing[1] 454 | self.log_debug(f'烙痕唤醒 黑名单 {black}') 455 | else: 456 | target = huanxing[0] 457 | if target.x < self.width_of_screen(0.4): 458 | self.log_info('点击左边唤醒') 459 | boxes = find_boxes_within_boundary(boxes, self.box_of_screen(0, 0, 0.5, 1)) 460 | self.click_relative(0.27, 0.71) 461 | elif target.x > self.width_of_screen(0.55): 462 | self.log_info('点击右边唤醒') 463 | boxes = find_boxes_within_boundary(boxes, self.box_of_screen(0.5, 0, 1, 1)) 464 | else: 465 | boxes = find_boxes_within_boundary(boxes, self.box_of_screen(0.25, 0, 0.73, 1)) 466 | self.log_info('点击中间唤醒') 467 | 468 | stats_up_choices = self.find_stats_up(boxes) 469 | if stats_up_choices and self.handle_stats_up(stats_up_choices): 470 | return 471 | 472 | def do_find_choices(self): 473 | choices = self.ocr(box=self.choice_zone) 474 | for i in range(len(choices) - 1, -1, -1): 475 | percent = calculate_color_percentage(self.frame, text_white_color, box=choices[i]) 476 | if percent < 0.02 or percent > 0.3: 477 | self.log_debug(f'choice is not in foreground {percent}, remove {choices[i]}') 478 | del choices[i] 479 | return True 480 | self.logger.debug(f"检测选项区域结果: {choices}") 481 | return choices 482 | 483 | def find_choices(self): 484 | return self.wait_until(self.do_find_choices, time_out=10) 485 | 486 | def find_depth(self, boxes=None): 487 | if boxes is None: 488 | boxes = self.ocr(box=self.dialog_zone) 489 | depth_box = None 490 | depth = 0 491 | numbers = find_boxes_by_name(boxes, re.compile(r"^[01D][0-9]$")) 492 | for number in numbers: 493 | # 居中大字深度 494 | if self.box_in_horizontal_center(number, off_percent=0.8) and number.height / self.height > 0.07: 495 | depth_box = number 496 | break 497 | if depth_box is not None: 498 | depth_box.name = depth_box.name.replace("D", "2") 499 | depth = int(depth_box.name) 500 | self.info['漫巡深度'] = depth 501 | elif find_box_by_name(boxes, "深度等级") is not None: # 弹窗可能出现太快 无法找到depth_box, 有深度等级的话就继续 502 | depth = self.info.get('漫巡深度', 0) 503 | return depth 504 | 505 | def click_cancel(self): 506 | self.click_relative(0.5, 0.1) 507 | 508 | def find_highest_gaowei_number(self, boxes): 509 | boxes = self.filter_gaowei_number_zone(boxes) 510 | # 文字转数字 511 | current_stats = [] 512 | for box in boxes: 513 | if stats := get_current_stats(box.name): 514 | current_stats.append(stats) 515 | if len(current_stats) != 5: 516 | self.log_error(f"没有找到五个属性 {current_stats}") 517 | current_stats = self.info.get('当前属性', [0, 0, 0, 0, 0]) 518 | else: 519 | self.update_stats_for_anjiao(current_stats) 520 | self.info['当前属性'] = current_stats 521 | tisheng_boxes = find_boxes_by_name(boxes, re.compile(r"^提升")) 522 | if len(tisheng_boxes) != 5: 523 | raise Exception("没有找到五个提升") 524 | avg_width = average_width(tisheng_boxes) 525 | self.log_debug(f"高维:目前数值 {current_stats} {tisheng_boxes}") 526 | highest_priority = -2 527 | highest_index = 0 528 | stats_priority = self.stats_priority(True) 529 | for i in range(5): 530 | box, yellow_line, gray_line = self.locate_gaowei_line(tisheng_boxes[i], avg_width) 531 | if current_stats[i] >= 1000: 532 | yellow_line = yellow_line / 2 533 | if gray_line == 0: # 全点亮或者没有对应卡 534 | if yellow_line > 0: # 全点亮 以黄线数量为优先级 535 | priority = 1000 * yellow_line 536 | else: # 没对应卡 最低优先级 537 | priority = -1 538 | else: # 有灰色线, 以黄线数量为优先级 539 | priority = 100 * yellow_line 540 | stats_priority_index = find_index(tisheng_boxes[i].name.replace('提升', ''), stats_priority) 541 | if stats_priority_index != -1: 542 | priority += 10 - stats_priority_index 543 | if current_stats[i] >= 1280 or (i == 4 and current_stats[i] >= self.zhongduan_max): ##超过上限或者终端超过900不点 544 | priority = -1 545 | if priority > highest_priority: 546 | highest_index = i 547 | highest_priority = priority 548 | self.log_debug(f"高维属性 {box} {yellow_line} {gray_line} {priority}") 549 | self.log_debug(f"最高优先级 高维 {tisheng_boxes[highest_index]} {highest_priority} {stats_priority}") 550 | self.draw_boxes("ocr", boxes) 551 | return tisheng_boxes[highest_index] 552 | 553 | def locate_gaowei_line(self, box, width): 554 | box = box.copy(height_offset=-box.height * 0.8, 555 | name=f"{box.name}_search_line") 556 | box.width = width 557 | box.y = self.height_of_screen(0.592) 558 | gray_percentage = calculate_color_percentage(self.frame, gray_color, box) * 100 559 | yellow_percentage = calculate_color_percentage(self.frame, yellow_color, box) * 100 560 | gray_line = (gray_percentage + gray_percent_per_line / 2) / gray_percent_per_line 561 | yellow_line = (yellow_percentage + yellow_percent_per_line / 2) / yellow_percent_per_line 562 | # self.log_debug(f"高维点亮 {box} {yellow_line} {yellow_percentage} {yellow_percent_per_line}") 563 | self.draw_boxes(boxes=box, color="blue") 564 | return box, int(yellow_line), int(gray_line) 565 | 566 | # 寻找灰色, 如有则降低优先级 567 | 568 | def parse_stats_choices(self, boxes): 569 | stats_list = [] 570 | for box in boxes: 571 | # Find all matches of the pattern in the input string 572 | for match in re.finditer(self.stats_up_re, box.name): 573 | attribute, start, end = match.groups() 574 | # Calculate the value. If 'end' is None, use 'start'; otherwise, calculate the average. 575 | value = int(start) if not end else (int(start) + int(end)) / 2 576 | stats_list.append((attribute, value, box)) 577 | 578 | return sorted(stats_list, key=lambda x: x[1], reverse=True) 579 | 580 | def is_black_text(self, box): 581 | black_percentage = calculate_color_percentage(self.frame, black_color, box) 582 | return black_percentage >= 0.02 583 | 584 | 585 | def find_priority_string(input_list, priority_list, start_index=-1): 586 | # 不在priority_list 为最高优先级 587 | for i in range(start_index, -len(input_list) - 1, -1): 588 | in_list = False 589 | for priority in priority_list: 590 | if priority in input_list[i].name: 591 | in_list = True 592 | if not in_list: 593 | return i 594 | for priority in priority_list: 595 | for i in range(start_index, -len(input_list) - 1, -1): 596 | # If the current string is in the priority list 597 | if priority in input_list[i].name: 598 | # Return the negative index of the string in the input list 599 | return i 600 | return start_index 601 | 602 | 603 | def find_index(element, lst): 604 | try: 605 | return lst.index(element) 606 | except ValueError: 607 | return -1 608 | 609 | 610 | def remove_item(original_list, items_to_remove): 611 | if items_to_remove: 612 | return [item for item in original_list if item not in items_to_remove] 613 | else: 614 | return original_list 615 | 616 | 617 | def target_index_array(lst): 618 | # Pair each element with its index and sort by the element, then by the original index 619 | sorted_pairs = sorted((e, i) for i, e in enumerate(lst)) 620 | 621 | # Create a list to hold the target indices 622 | target_indices = [-1] * len(lst) 623 | 624 | # Assign continuous indices to the elements in the sorted list 625 | current_index = 0 626 | for _, original_index in sorted_pairs: 627 | if target_indices[original_index] == -1: 628 | target_indices[original_index] = current_index 629 | current_index += 1 630 | else: 631 | # If the number is equal to the previous, increment the current index 632 | current_index += 1 633 | target_indices[original_index] = current_index 634 | 635 | return target_indices 636 | 637 | 638 | def remove_non_digits_and_convert(s): 639 | # Use regular expression to keep only digits and '+' characters 640 | clean_string = re.sub(r'[^0-9+]', '', s) 641 | clean_string = clean_string.strip('+') 642 | # Evaluate the cleaned string as a mathematical expression 643 | try: 644 | return eval(clean_string) 645 | except Exception as e: 646 | return 0 647 | 648 | 649 | gray_percent_per_line = 0.03660270078 * 100 650 | yellow_percent_per_line = 0.02821869488 * 100 651 | 652 | green_color = { 653 | 'r': (132, 152), # Red range 654 | 'g': (222, 242), # Green range 655 | 'b': (166, 186) # Blue range 656 | } 657 | 658 | black_color = { 659 | 'r': (0, 30), # Red range 660 | 'g': (0, 30), # Green range 661 | 'b': (0, 30) # Blue range 662 | } 663 | 664 | gray_color = { 665 | 'r': (80, 105), # Red range 666 | 'g': (90, 110), # Green range 667 | 'b': (97, 120) # Blue range 668 | } 669 | 670 | yellow_color = { 671 | 'r': (220, 250), # Red range 672 | 'g': (180, 210), # Green range 673 | 'b': (90, 110) # Blue range 674 | } 675 | text_white_color = { 676 | 'r': (240, 255), # Red range 677 | 'g': (240, 255), # Green range 678 | 'b': (240, 255) # Blue range 679 | } 680 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc.