🎮 PS5 自动化挂机指南:基于 Mac + Chiaki-ng 视觉识别
本方案通过 Mac 上的 Chiaki-ng 串流 PS5 画面,利用 mss 高速截屏,结合 OpenCV 模板匹配与 EasyOCR 文字识别,判断游戏内状态,最后通过 pyautogui 模拟键盘操作,实现全自动的"找异界石 -> 召唤 -> 找人 -> 雇佣 -> 解雇 -> 送礼 -> 重置场景"闭环。
⚙️ 一、 基础环境配置 (Mac 专属)
1. Python 环境与依赖库安装
建议使用 Python 3.9 - 3.11 版本。打开 Mac 终端 (Terminal),执行以下命令安装所需的核心库:
bash
pip install opencv-python easyocr mss numpy pyautogui
注:首次运行脚本时,EasyOCR 会自动下载几 MB 的中英文识别模型(由于你代码中加了 ssl 忽略验证,国内网络报错的概率已大幅降低)。
2. ⚠️ Mac 核心系统权限设置 (极其重要)
如果缺少权限,脚本将无法截图,也无法控制键盘鼠标!
前往 Mac 的 系统设置 -> 隐私与安全性:
- 屏幕录制 (Screen Recording): 允许你运行脚本的终端(如 Terminal, iTerm, 或 PyCharm/VSCode)进行录屏(为了
mss能截取 Chiaki 画面)。 - 辅助功能 (Accessibility): 允许终端或代码编辑器控制你的电脑(为了
pyautogui能模拟按键)。
3. Chiaki-ng 画面设置
- 分辨率与位置: 代码中
sct.monitors[1]默认抓取主屏幕。请确保 Chiaki-ng 处于 全屏模式 或者 严格对齐屏幕左上角最大化。 - 网络要求: 尽量在内网 5G Wi-Fi 下串流,画面模糊或马赛克会导致 OpenCV 图像匹配和 OCR 识别失败。
🖼️ 二、 需要你自己准备的图片素材 (模板匹配)
代码中使用了 4 张 UI 截图作为 OpenCV 的匹配模板。你必须自己在 Mac 全屏/固定窗口状态下 截图并裁剪,保存到与 Python 脚本 同一目录 下。
截图裁剪技巧 (Mac): 使用
Command + Shift + 4框选目标区域。尽量只框选文字或图标高亮部分 ,不要包含太多复杂的背景,这样Canny边缘检测会更准确。
你需要准备以下 4 张 .jpg 图片:
-
📸
UI_O_riftstone.jpg- 作用: 靠近异界石时弹出的【调查】交互提示。

-
📸
UI_find_*pawn的名字*.jpg(需特别适配!)- 作用: 用于定位特定随从。
- 怎么截: 召唤出目标随从后,截取她/他头顶悬浮的 名字名牌。
-
📸
UI_O_pawn.jpg- 作用: 贴近随从时弹出的【打招呼 / 对话】交互提示。

- 作用: 贴近随从时弹出的【打招呼 / 对话】交互提示。
-
📸
UI_Exit_the_Rift.jpg- 作用: 防止角色乱跑撞到地图边界弹出的"是否离开异界"的提示。

- 作用: 防止角色乱跑撞到地图边界弹出的"是否离开异界"的提示。
🛠️ 三、 代码中需要你专门修改/配置的参数
在你的代码中,有几个地方是与你的具体游戏进度、随从名字和背包状态深度绑定的,换人或换号时必须修改:
1. 随从名字 OCR 判断 (核心)
python
if "polay" in text_lower or "librom" in text_lower: #判断条件需特别适配
- 修改说明: EasyOCR 识别到界面文本后,会全部转换为小写 (
text_lower) 进行比对。你需要把"polay"和"librom"替换为你真正要找的随从名字的 纯小写英文字母。 - 补充提示:如果你的游戏语言是繁体中文,建议把初始化引擎
easyocr.Reader(['ch_sim', 'en'])中的ch_sim改为ch_tra(繁体中文)。
2. 送礼坐标/按键操作 (需特别适配)
python
print("▶️ 开始执行 送礼------需根据礼物位置调整代码")#判断条件需特别适配
tap_key("l")
tap_key("l")
tap_key("/")
- 修改说明: 这里的
l键和/键是用来在背包界面中翻页、选择礼物的宏。因为每次你的背包物品排列可能不同,你需要根据你要送的礼物(如:花束、特定材料)在背包中的实际位置,重新编排按下了几次翻页、几次方向键。
3. 多显示器捕捉设定
定位到主循环开头的 with mss.mss() as sct: 内部:
python
monitor = sct.monitors[1]
- 修改说明:
monitors[1]代表你的主屏幕(Mac自带屏幕)。如果你外接了显示器,且 Chiaki-ng 运行在外接显示器上,可能需要改为monitors[2]。
4. Chiaki-ng 的键盘映射匹配 (极为关键)
你的脚本全篇在使用键盘按键来控制 PS5 游戏,例如:
/键:确认/交互/攻击?'键 (单引号):取消/冲刺/解雇?k键:菜单特定按钮?w, a, s, d, left, right:移动和视角转动。q, [, ':战士盾牌猛击组合键。
你需要打开 Chiaki-ng 的设置面板 (Settings -> Key Bindings),确保你 Mac 键盘上的这些按键,准确无误地映射到了 PS5 手柄对应的按键上 (十字键、摇杆、圈叉方三角等)。
🚨 四、 运行测试与安全机制
1. 启动脚本
打开终端,运行:
bash
python your_script_name.py
终端会提示 ⏳ 正在初始化 OCR 引擎... 和 👉 3秒后接管...。
在这 3 秒内,立刻点击 Chiaki-ng 的窗口,使其成为当前活动窗口。
2. 物理急停机制 (Failsafe)
在代码中开启了:
python
pyautogui.FAILSAFE = True
遇到卡死怎么停?
无论脚本在疯狂乱点还是乱转,你只需要强行滑动鼠标,把鼠标指针甩到 Mac 屏幕的最左上角(像素坐标 0,0 处) ,pyautogui 就会立即抛出异常并强制停止脚本运行。
3. 日志排错 (Debug)
代码中写了 🐞 [DEBUG] 输出信息。如果脚本卡在某个状态不动:
- 看终端最后停留的 DEBUG 状态。
- 如果是
未发现 pawn名牌,说明UI_find_Polay.jpg没截好,或者相似度阈值FIND_THRESHOLD = 0.40设置得太高/太低,可以尝试调整数值。 - 如果提示
OCR均未直接进入雇佣界面,检查网络串流画质是否拉胯导致文字模糊。
🚨 五、 原初代码
python
import sys # 内置系统接口模块 | 简介:处理解释器与操作系统交互。作用:用于在检测到错误时调用 sys.exit() 安全退出脚本。
import time # 内置时间控制模块 | 简介:提供时间相关功能。作用:使用 time.sleep() 设置延迟,防止脚本操作过快导致微信界面卡顿。
import ssl # 内置安全套接层模块 | 简介:处理网络加密协议。作用:通常用于修复 EasyOCR 首次下载模型时因 HTTPS 证书引发的报错。
import cv2 # OpenCV 计算机视觉库 | 简介:强大的开源图像处理库。作用:对屏幕截图进行灰度化、图像匹配、裁剪等预处理,提高识别率。
import easyocr # EasyOCR 文字识别库 | 简介:基于深度学习的通用 OCR 工具。作用:通过"看"屏幕直接识别微信好友姓名、金额等文字,获取其坐标。
import mss # MSS 超快屏幕截图库 | 简介:极速跨平台截屏工具。作用:以极高帧率捕获 Mac 屏幕画面,提供比原生 pyautogui 更快的图像源。
import numpy as np # NumPy 矩阵运算数学库 | 简介:高性能科学计算核心库。作用:将 mss 捕获的屏幕原始像素数据高速转换为 OpenCV 可处理的图像矩阵。
import pyautogui # PyAutoGUI 自动化控制库 | 简介:模拟鼠标键盘的经典库。作用:执行最终的鼠标点击、移动以及用键盘输入金额和 Tab 键。
# ================= 循环设置 =================
MAX_LOOPS = 10
ssl._create_default_https_context = ssl._create_unverified_context
# ========================================================================
# 【优化 1】遇到卡死把鼠标甩到屏幕最左上角就能强停!
pyautogui.FAILSAFE = True
# ==========================================
print("⏳ 正在初始化 OCR 引擎,请稍候 (首次运行会自动下载几MB的模型文件)...")
reader = easyocr.Reader(['ch_sim', 'en'])
# 加载相关UI模板
try:
template_pawn_interact = cv2.imread("UI_O_pawn.jpg", 0)
template_find_polay = cv2.imread("UI_find_Polay.jpg", 0)#需特别适配
template_o_riftstone = cv2.imread("UI_O_riftstone.jpg", 0)
template_exit_rift = cv2.imread("UI_Exit_the_Rift.jpg", 0)
h_polay, w_polay = template_find_polay.shape
except Exception as e:
print("⚠️ 找不到模板图片,请确保当前目录下有对应图片!错误信息:", e)
sys.exit()
FIND_THRESHOLD = 0.40
DEADZONE_PIXELS = 200
print(f"⚠️ Mac (2560x1600) 终极自动化闭环启动!")
print(f"🔄 设定循环次数: {'无限循环' if MAX_LOOPS == 0 else f'{MAX_LOOPS} 次'}")
print("👉 3秒后接管,请确保 Chiaki 游戏画面处于【全屏】或【左上角最大化】状态。")
time.sleep(3)
def check_ui_prompt(gray_screen, template, threshold=0.45):
"""用于锐利边缘的 UI 文本检测"""
if template is None:
return False, None
edges_screen = cv2.Canny(gray_screen, 100, 200)
edges_template = cv2.Canny(template, 100, 200)
res = cv2.matchTemplate(edges_screen, edges_template, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
if max_val >= threshold:
return True, max_loc
return False, None
# ================= 按键辅助函数 =================
def tap_key(key, hold_time=0.02, wait_time=1.0):
print(f" -> 短按 [{key}]")
pyautogui.keyDown(key)
time.sleep(hold_time)
pyautogui.keyUp(key)
time.sleep(wait_time)
def long_press_key(key, hold_time=1.5, wait_time=0.5):
print(f" -> 长按 [{key}] ({hold_time}秒)")
pyautogui.keyDown(key)
time.sleep(hold_time)
pyautogui.keyUp(key)
time.sleep(wait_time)
# --- 新增:专门用于同时按下多个按键的宏 ---
def tap_keys_simultaneously(keys, hold_time=0.1, wait_time=1.5):
keys_str = " + ".join(keys)
print(f" -> ⚡ 触发组合突进技能: 同时短按 [{keys_str}]")# 突进技能------战士的盾牌猛击
for key in keys:
pyautogui.keyDown(key)
time.sleep(hold_time)
for key in keys:
pyautogui.keyUp(key)
time.sleep(wait_time)
# ================================================
def start_tracking():
current_loop = 0
CURRENT_STATE = "FIND_RIFTSTONE"
print(f"🐞 [DEBUG] 脚本进入主循环,初始状态设定为: {CURRENT_STATE}")
is_near = False
has_dashed = False # --- 新增:标记是否已经使用过突进技能 ---
with mss.mss() as sct:
monitor = sct.monitors[1]
while True:
# ==================================================
# 状态 0:寻找异界石并靠近(纯技能突进版)
# ==================================================
if CURRENT_STATE == "FIND_RIFTSTONE":
sct_img = sct.grab(monitor)
img = np.array(sct_img)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
is_stone_prompt, _ = check_ui_prompt(img_gray, template_o_riftstone)
if is_stone_prompt:
print("🐞 [DEBUG] 发现 [调查] 异界石提示,刹车并验证 1 秒稳定性...")
stable = True
for _ in range(5):
time.sleep(0.2)
sct_img_v = sct.grab(monitor)
img_gray_v = cv2.cvtColor(np.array(sct_img_v), cv2.COLOR_BGRA2GRAY)
prompt_still_there, _ = check_ui_prompt(img_gray_v, template_o_riftstone)
if not prompt_still_there:
stable = False
break
if stable:
print("🛑 贴脸确认成功!稳定持续 1 秒。")
time.sleep(1.0)
print("🐞 [DEBUG] 状态切换: FIND_RIFTSTONE -> SUMMON_PAWN")
CURRENT_STATE = "SUMMON_PAWN"
is_near = False
continue
else:
print("⚠️ [调查] 提示闪退!说明已在异界石极近距离,进入微调靠近模式。")
is_near = True
# 如果没有看到稳定的 [调查] 提示,决定如何移动
if not is_stone_prompt or not stable:
if is_near:
# 距离极近(UI 闪退过),只按右转走半步,防止突进飞出去
print("🐞 [DEBUG] [找异界石] 极近距离微调,转动视角...")
pyautogui.keyDown('right')
time.sleep(0.05)
pyautogui.keyUp('right')
time.sleep(0.2)
elif not has_dashed:
# --- 还没突进过,执行突进并标记 ---
print("🐞 [DEBUG] [找异界石] 视野内未发现稳定 [调查] 提示,释放组合突进技能靠近...")
tap_key("tab", hold_time=0.1, wait_time=1.5)
tap_keys_simultaneously(['q', '[', "'"], hold_time=0.1, wait_time=1.5)
has_dashed = True
else:
# --- 已经突进过了,原地转动视角寻找 ---
print("🐞 [DEBUG] [找异界石] 已执行过突进,向右转动视角寻找 [调查] 提示...")
pyautogui.keyDown('right')
time.sleep(0.1)
pyautogui.keyUp('right')
time.sleep(0.02)
# ==================================================
# 状态 1:异界石交互
# ==================================================
elif CURRENT_STATE == "SUMMON_PAWN":
print("\n=====================================")
print(f"🔄 第 {current_loop + 1} 次循环开始:召唤目标随从")
print("=====================================")
print("🐞 [DEBUG] 正在执行 SUMMON_PAWN 盲按连招")
print("🪨 开始异界石交互连招...")
long_press_key("'", hold_time=3)
time.sleep(7.0)
long_press_key("'", hold_time=2)
time.sleep(2.0)
tap_key("/")
tap_key("k")
tap_key("/")
time.sleep(7.0) # 特别关注的追随者加载时间长
tap_key("/")
tap_key("k")
tap_key("/")
time.sleep(5.0)
print("🐞 [DEBUG] 状态切换: SUMMON_PAWN -> FIND_PAWN")
CURRENT_STATE = "FIND_PAWN"
is_near = False
continue
# ==================================================
# 状态 2:寻找随从与互动
# ==================================================
elif CURRENT_STATE == "FIND_PAWN":
sct_img = sct.grab(monitor)
img = np.array(sct_img)
screen_center_x = img.shape[1] // 2
img_gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
# ==================================================
# --- 寻找随从期间的边界误碰防护 ---
# ==================================================
is_exit_prompt, _ = check_ui_prompt(img_gray, template_exit_rift)
if is_exit_prompt:
print("🛑 🐞 [DEBUG] [找随从] 意外撞到地图边界!触发退出提示,执行纠正操作...")
pyautogui.keyUp('w')
pyautogui.keyUp('left')
pyautogui.keyUp('right')
time.sleep(0.5)
tap_key("k")
tap_key("/")
long_press_key("s", hold_time=1.5)
time.sleep(1.0)
print("🔄 🐞 [DEBUG] [找随从] 纠正完毕,继续寻找随从...")
continue
# ==================================================
is_pawn_prompt, _ = check_ui_prompt(img_gray, template_pawn_interact)
if is_pawn_prompt:
print("🐞 [DEBUG] 发现 [打招呼] 提示,刹车并验证 1 秒稳定性...")
pyautogui.keyUp('w')
pyautogui.keyUp('left')
pyautogui.keyUp('right')
stable = True
for _ in range(5):
time.sleep(0.2)
sct_img_v = sct.grab(monitor)
img_gray_v = cv2.cvtColor(np.array(sct_img_v), cv2.COLOR_BGRA2GRAY)
prompt_still_there, _ = check_ui_prompt(img_gray_v, template_pawn_interact)
if not prompt_still_there:
stable = False
break
if stable:
print("🛑 贴脸确认成功!稳定持续 1 秒。")
long_press_key("'", hold_time=1.5)
print("⏳ 等待 1 秒让对话 UI 加载...")
time.sleep(1)
tap_key("/")
is_near = False
else:
print("⚠️ [打招呼] 提示闪退!说明已在随从极近距离,进入微调靠近模式。")
is_near = True
continue
is_hire_ui = False
print("🔎 开始执行 OCR 文字扫描 (最多尝试 3 次)...")
for attempt in range(1, 4):
sct_img_hire = sct.grab(monitor)
img_bgr_hire = np.array(sct_img_hire)
h, w = img_bgr_hire.shape[:2]
roi = img_bgr_hire[int(h * 0.1):int(h * 0.9), int(w * 0):int(w * 0.3)]
ocr_results = reader.readtext(roi, detail=0)
print(f"🖨️ [OCR 调试 - 第{attempt}次] 当前区域识别到全部文本: {ocr_results}")
for text in ocr_results:
# 必须全部转为小写对比,所以判断条件里也得是全小写的 polay 和 librom
text_lower = text.lower()
if "polay" in text_lower or "librom" in text_lower:#判断条件需特别适配
is_hire_ui = True
print(f"🎯 [OCR确认] 第 {attempt} 次扫描找到关键文字: '{text}'")
break
if is_hire_ui:
break
if attempt < 3:
print("⏳ 画面可能未完全加载,0.5秒后重试...")
time.sleep(0.5)
if not is_hire_ui:
print("⚠️ 3次 OCR 均未直接进入雇佣界面,执行纠正操作:(i -> /)")
tap_key("i")
tap_key("/")
print("⏳ 纠正完成,等待 1.5 秒动画...")
time.sleep(2)
long_press_key("w", hold_time=0.5)
print("🔄 重新进入找人流程...")
print("🐞 [DEBUG] 纠正完毕,强制重启 FIND_PAWN 流程")
continue
else:
print("✅ 确认已在雇佣界面!")
print("▶️ 开始执行 雇佣")
tap_key("/")
tap_key("/")
print("▶️ 开始执行 解雇")
long_press_key("'", hold_time=1.5)
tap_key("/")
tap_key("k")
tap_key("/")
print("▶️ 开始执行 送礼------需根据礼物位置调整代码")#判断条件需特别适配
tap_key("l")
tap_key("l")
tap_key("/")
print("▶️ 开始执行 确认送礼")
tap_key("i")
tap_key("/")
tap_key("/")
tap_key("/")
tap_key("/")
print("✅ 随从交互与送礼流程全部完成!等待 1 秒...")
time.sleep(1.0)
print("🐞 [DEBUG] 状态切换: FIND_PAWN -> RESET_SCENE")
CURRENT_STATE = "RESET_SCENE"
continue
target_found = False
offset_x = 0
found, max_loc = check_ui_prompt(img_gray, template_find_polay, threshold=FIND_THRESHOLD)
if found:
target_found = True
target_bottom_right_x = max_loc[0] + w_polay
offset_x = target_bottom_right_x - screen_center_x
if target_found:
if offset_x < -DEADZONE_PIXELS:
print(f"🐞 [DEBUG] [找随从] Polay名牌在左侧 (偏移 {offset_x:.1f}),向左微调...")
pyautogui.keyDown('left')
pyautogui.keyDown('w')
time.sleep(0.05)
pyautogui.keyUp('left')
pyautogui.keyUp('w')
elif offset_x > DEADZONE_PIXELS:
print(f"🐞 [DEBUG] [找随从] Polay名牌在右侧 (偏移 {offset_x:.1f}),向右微调...")
pyautogui.keyDown('right')
pyautogui.keyDown('w')
time.sleep(0.05)
pyautogui.keyUp('right')
pyautogui.keyUp('w')
else:
move_duration = 0.05 if is_near else 0.15
print(f"🐞 [DEBUG] [找随从] Polay名牌在正前方 (偏移 {offset_x:.1f}),向前移动 (时长: {move_duration}s)...")
pyautogui.keyDown('w')
time.sleep(move_duration)
pyautogui.keyUp('w')
else:
print("🐞 [DEBUG] [找随从] 视野内未发现Polay名牌,向右转动视角寻找中...")
pyautogui.keyDown('right')
time.sleep(0.1)
pyautogui.keyUp('right')
time.sleep(0.02)
# ==================================================
# 状态 3:盲跑撞墙重置位置
# ==================================================
elif CURRENT_STATE == "RESET_SCENE":
print("\n=====================================")
print("🐞 [DEBUG] 正在执行 RESET_SCENE 盲跑重置")
print("🏃♂️ 开始长跑重置 (按住 W 键 10 秒)...")
pyautogui.keyDown('w')
time.sleep(10)
pyautogui.keyUp('w')
print("🛑 已跑满 10 秒,松开 W 键。等待 UI 弹出...")
time.sleep(1.0)
print("🔙 执行重置:按下 / 并等待场景刷新...")
tap_key("/")
time.sleep(3.0)
current_loop += 1
print(f"🏆 一次完整循环结束!当前已完成: {current_loop} 次")
if MAX_LOOPS > 0 and current_loop >= MAX_LOOPS:
print(f"🎉 已达到设定的最大循环次数 ({MAX_LOOPS}),自动挂机结束!")
sys.exit()
else:
print("🐞 [DEBUG] 状态切换: RESET_SCENE -> FIND_RIFTSTONE")
CURRENT_STATE = "FIND_RIFTSTONE"
is_near = False
has_dashed = False # --- 新增:重新寻找异界石时,重置突进标记 ---
if __name__ == '__main__':
start_tracking()
🚀 六、 最新进阶版代码(动态寻路 & 智能送礼版)
在经历了多次迭代后,最新版本的代码进行了大幅度的底层逻辑重构,引入了更多计算机视觉与自动化高级特性,强烈建议直接复制此版本使用。以下为核心更新亮点:
- 开局交互式配置选单 :脚本启动时会在终端提供选项卡,直接输入
1或2即可一键切换目标随从(Polay 或 PYL)。代码会自动绑定并加载对应的名牌图片、OCR关键词以及送礼宏,免去了每次换人都要修改源代码的烦恼。 - 三层优先级动态 OCR 送礼策略:摒弃了死板的背包坐标记录,改用 EasyOCR 动态扫描背包列表。设定 T1/T2/T3 三级优先级(如诺根/金龟/统治者护甲),前5次循环强制寻找最高优物资,并在遍历后智能降级兜底;一旦检测到已送出过特定物品(如护甲),系统会动态将其拉高至最高优先级,真正实现智能"清包"。
- 极限衰减版 IBVS 向量寻路 :重构了贴近目标时的追踪逻辑,引入欧氏距离和偏转角计算。当极近距离靠近目标时,加入"动态衰减死区 (
dampener)",使得移动控制(w)与镜头修正(left/right)在不同的独立时间轴上异步计算,彻底解决了原版本镜头转动过冲导致的"钟摆绕行"死循环。 - 动态亮度 UI 识别与防卡死机制 :
- 亮度检测 (
np.mean):由于地图边界提示框可能被误触,现在通过切割"是/否"按钮区域,比对像素平均亮度来精确判断当前哪个选项处于高亮状态,确保 100% 安全执行取消或退出指令。 - 两阶段防卡死脱逃:撞墙重置加入了更立体的分阶段盲跑(先冲正前方10秒,如果地形卡死,自动进入二阶段随机向侧后方绕行),同时找人时若连续前进9次未触发对话,会自动执行防卡死侧滑,鲁棒性拉满!
- 亮度检测 (
完整进阶代码:
python
import mss
import numpy as np
import cv2
import time
import pyautogui
import sys
import easyocr
import ssl
import random
import math # 用于计算向量角度和欧氏距离
# ================= 循环设置 =================
MAX_LOOPS = 100000
ssl._create_default_https_context = ssl._create_unverified_context
# ========================================================================
# 【优化 1】遇到卡死把鼠标甩到屏幕最左上角就能强停!
pyautogui.FAILSAFE = True
# ================= 目标随从选择配置 =================
print("\n=============================================")
print("请选择本次要挂机交互的目标随从:")
print(" [1] Polay (OCR匹配: polay/librom)")
print(" [2] PYL (OCR匹配: pyl/ash)")
print("=============================================")
user_choice = input("请输入 1 或 2 (按回车默认选择 1): ").strip()
if user_choice == '2':
TARGET_DISPLAY_NAME = "PYL"
TEMPLATE_FIND_IMG = "UI_find_PYL.jpg"
OCR_KEYWORDS = ["pyl", "ash"]
else:
TARGET_DISPLAY_NAME = "Polay"
TEMPLATE_FIND_IMG = "UI_find_Polay.jpg"
OCR_KEYWORDS = ["polay", "librom"]
print(f"\n✅ 已锁定目标: {TARGET_DISPLAY_NAME} ! 将使用对应的配置进行挂机。")
print("🎁 送礼策略:已启用 OCR 三层优先级动态智能识别算法 (前5次强制寻T1)!")
print("=============================================\n")
# ===================================================
print("⏳ 正在初始化 OCR 引擎,请稍候 (首次运行会自动下载几MB的模型文件)...")
reader = easyocr.Reader(['ch_sim', 'en'])
# 加载相关UI模板
try:
template_pawn_interact = cv2.imread("UI_O_pawn.jpg", 0)
template_find_target = cv2.imread(TEMPLATE_FIND_IMG, 0)
template_o_riftstone = cv2.imread("UI_O_riftstone.jpg", 0)
template_exit_rift = cv2.imread("UI_Exit_the_Rift.jpg", 0)
h_target, w_target = template_find_target.shape
except Exception as e:
print(f"⚠️ 找不到模板图片,请确保当前目录下有对应图片!错误信息: {e}")
sys.exit()
FIND_THRESHOLD = 0.40
print(f"⚠️ Mac (2560x1600) 终极自动化闭环启动!")
print(f"🔄 设定循环次数: {'无限循环' if MAX_LOOPS == 0 else f'{MAX_LOOPS} 次'}")
print("👉 3秒后接管,请确保 Chiaki 游戏画面处于【全屏】或【左上角最大化】状态。")
time.sleep(3)
# --- 函数返回 3 个值(包含置信度 max_val) ---
def check_ui_prompt(gray_screen, template, threshold=0.45):
if template is None:
return False, None, 0
edges_screen = cv2.Canny(gray_screen, 100, 200)
edges_template = cv2.Canny(template, 100, 200)
res = cv2.matchTemplate(edges_screen, edges_template, cv2.TM_CCOEFF_NORMED)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
if max_val >= threshold:
return True, max_loc, max_val
return False, None, max_val
# ================= 按键辅助函数 =================
def tap_key(key, hold_time=0.02, wait_time=1.0):
print(f" -> 短按 [{key}]")
pyautogui.keyDown(key)
time.sleep(hold_time)
pyautogui.keyUp(key)
time.sleep(wait_time)
def long_press_key(key, hold_time=1.5, wait_time=0.5):
print(f" -> 长按 [{key}] ({hold_time}秒)")
pyautogui.keyDown(key)
time.sleep(hold_time)
pyautogui.keyUp(key)
time.sleep(wait_time)
def tap_keys_simultaneously(keys, hold_time=0.1, wait_time=1.5):
keys_str = " + ".join(keys)
print(f" -> ⚡ 触发组合突进技能: 同时短按 [{keys_str}]")
for key in keys:
pyautogui.keyDown(key)
time.sleep(hold_time)
for key in keys:
pyautogui.keyUp(key)
time.sleep(wait_time)
# ================================================
def start_tracking():
current_loop = 0
# 修改点:为了 Debug 效率,初始状态改为直接寻找随从
CURRENT_STATE = "FIND_PAWN"
print(f"🐞 [DEBUG] 脚本进入主循环,初始状态设定为: {CURRENT_STATE}")
is_near = False
has_dashed = False
has_seen_pawn = False
forward_w_count = 0
near_turn_count = 0
# 【核心变量】:全局动态送礼状态记录
has_gifted_ruler_armor = False
with mss.mss() as sct:
monitor = sct.monitors[1]
while True:
# ==================================================
# 状态 0:寻找异界石并靠近
# ==================================================
if CURRENT_STATE == "FIND_RIFTSTONE":
sct_img = sct.grab(monitor)
img = np.array(sct_img)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
is_stone_prompt, _, _ = check_ui_prompt(img_gray, template_o_riftstone, threshold=0.45)
if is_stone_prompt:
print("🐞 [DEBUG] 发现 [调查] 异界石提示,刹车并验证 1 秒稳定性...")
stable = True
for _ in range(5):
time.sleep(0.2)
sct_img_v = sct.grab(monitor)
img_gray_v = cv2.cvtColor(np.array(sct_img_v), cv2.COLOR_BGRA2GRAY)
prompt_still_there, _, _ = check_ui_prompt(img_gray_v, template_o_riftstone, threshold=0.45)
if not prompt_still_there:
stable = False
break
if stable:
print("🛑 贴脸确认成功!稳定持续 1 秒。")
time.sleep(1.0)
print("🐞 [DEBUG] 状态切换: FIND_RIFTSTONE -> SUMMON_PAWN")
CURRENT_STATE = "SUMMON_PAWN"
is_near = False
near_turn_count = 0
continue
else:
print("⚠️ [调查] 提示闪退!说明已在异界石极近距离,进入微调靠近模式。")
is_near = True
if not is_stone_prompt or not stable:
if is_near:
print("🐞 [DEBUG] [找异界石] 极近距离微调,转动视角...")
pyautogui.keyDown('right')
time.sleep(0.05)
pyautogui.keyUp('right')
time.sleep(0.2)
elif not has_dashed:
print("🐞 [DEBUG] [找异界石] 视野内未发现稳定 [调查] 提示,释放组合突进技能靠近...")
tap_key("tab", hold_time=0.1, wait_time=1.5)
tap_keys_simultaneously(['q', '[', "'"], hold_time=0.1, wait_time=1.5)
has_dashed = True
else:
print("🐞 [DEBUG] [找异界石] 已执行过突进,向右转动视角寻找 [调查] 提示...")
pyautogui.keyDown('right')
time.sleep(0.1)
pyautogui.keyUp('right')
time.sleep(0.02)
# ==================================================
# 状态 1:异界石交互
# ==================================================
elif CURRENT_STATE == "SUMMON_PAWN":
print("\n=====================================")
print(f"🔄 第 {current_loop + 1} 次循环开始:召唤目标随从 ({TARGET_DISPLAY_NAME})")
print("=====================================")
print("🐞 [DEBUG] 正在执行 SUMMON_PAWN 盲按连招")
print("🪨 开始异界石交互连招...")
long_press_key("'", hold_time=3)
time.sleep(10)
long_press_key("'", hold_time=2)
time.sleep(2.0)
tap_key("/")
tap_key("k")
tap_key("/")
# ==================================================
# --- 动态 OCR 检测特别关注列表加载 ---
# ==================================================
is_list_loaded = False
print(f"🔎 正在等待并扫描特别关注列表加载 (最多尝试 40 次,约 20 秒)...")
for attempt in range(1, 41):
sct_img_list = sct.grab(monitor)
img_bgr_list = np.array(sct_img_list)
h, w = img_bgr_list.shape[:2]
roi = img_bgr_list[int(h * 0.1):int(h * 0.9), int(w * 0):int(w * 0.3)]
ocr_results = reader.readtext(roi, detail=0)
print(f"🖨️ [OCR 调试 - 列表等待 第{attempt}次] 当前区域识别到: {ocr_results}")
for text in ocr_results:
text_lower = text.lower()
if any(keyword in text_lower for keyword in OCR_KEYWORDS):
is_list_loaded = True
print(f"🎯 [OCR确认] 列表已加载!找到目标名字: '{text}'")
break
if is_list_loaded:
break
if attempt < 40:
time.sleep(0.5)
if not is_list_loaded:
print(f"⚠️ [警告] 20 秒后 OCR 仍未在列表中识别到 {TARGET_DISPLAY_NAME},将强行执行后续盲按...")
else:
print("✅ 特别关注列表加载完毕,继续执行召唤流程!")
tap_key("/")
tap_key("k")
tap_key("/")
time.sleep(5.0)
print("🐞 [DEBUG] 状态切换: SUMMON_PAWN -> FIND_PAWN")
CURRENT_STATE = "FIND_PAWN"
is_near = False
has_seen_pawn = False
forward_w_count = 0
near_turn_count = 0
continue
# ==================================================
# 状态 2:寻找随从与互动
# ==================================================
elif CURRENT_STATE == "FIND_PAWN":
sct_img = sct.grab(monitor)
img = np.array(sct_img)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGRA2GRAY)
# ==================================================
# --- 边界误碰防护(置信度二次确认 + 动态亮度判定) ---
# ==================================================
is_exit_prompt, _, exit_val = check_ui_prompt(img_gray, template_exit_rift, threshold=0.45)
trigger_exit = False
if is_exit_prompt:
if exit_val > 0.80:
trigger_exit = True
else:
print(f"⚠️ [DEBUG] 疑似边界提示 (置信度 {exit_val:.2f}),进行二次确认...")
time.sleep(0.3)
sct_img_v = sct.grab(monitor)
img_gray_v = cv2.cvtColor(np.array(sct_img_v), cv2.COLOR_BGRA2GRAY)
is_exit_prompt_2, _, _ = check_ui_prompt(img_gray_v, template_exit_rift, threshold=0.45)
if is_exit_prompt_2:
trigger_exit = True
if trigger_exit:
print("🛑 🐞 [DEBUG] [找随从] 意外撞到地图边界!触发退出提示,执行亮度纠正...")
for k in ['w', 'a', 's', 'd', 'left', 'right']: pyautogui.keyUp(k)
time.sleep(1.0)
sct_img_exit = sct.grab(monitor)
img_gray_exit = cv2.cvtColor(np.array(sct_img_exit), cv2.COLOR_BGRA2GRAY)
h, w = img_gray_exit.shape
roi_y1, roi_y2 = int(h * 0.4), int(h * 0.9)
roi_x1, roi_x2 = int(w * 0.4), int(w * 0.6)
roi_gray = img_gray_exit[roi_y1:roi_y2, roi_x1:roi_x2]
ocr_results = reader.readtext(roi_gray, detail=1)
shi_y, fou_y = None, None
for bbox, text, prob in ocr_results:
if '是' in text:
shi_y = (bbox[0][1] + bbox[2][1]) / 2 + roi_y1
elif '否' in text:
fou_y = (bbox[0][1] + bbox[2][1]) / 2 + roi_y1
if shi_y is not None and fou_y is not None:
for _ in range(5):
sct_img_check = sct.grab(monitor)
img_gray_check = cv2.cvtColor(np.array(sct_img_check), cv2.COLOR_BGRA2GRAY)
band_w = int(w * 0.3)
# 已安全恢复正确的坐标运算减号与加号
x_start = int(w / 2 - band_w / 2)
x_end = int(w / 2 + band_w / 2)
shi_band = img_gray_check[int(shi_y) - 10:int(shi_y) + 10, x_start:x_end]
fou_band = img_gray_check[int(fou_y) - 10:int(fou_y) + 10, x_start:x_end]
shi_lum = np.mean(shi_band)
fou_lum = np.mean(fou_band)
print(f" -> [亮度检测] '是'亮度: {shi_lum:.1f} | '否'亮度: {fou_lum:.1f}")
if fou_lum > shi_lum + 5:
print(" ✅ '否' 区域已泛白选中,按下 [/] 确认取消退出...")
tap_key("/")
time.sleep(2.0)
break
else:
print(" 🔄 '否' 未被选中,按下 [k] 切换选项...")
tap_key("k")
time.sleep(0.5)
else:
print(" ⚠️ 退出纠正循环超时结束,执行保底确认 [/]...")
tap_key("/")
time.sleep(2.0)
else:
print(" ⚠️ OCR未能同时定位'是/否',执行盲按切 [k] 然后确认...")
tap_key("k")
time.sleep(0.5)
tap_key("/")
time.sleep(2.0)
print("🔄 🐞 [DEBUG] [找随从] 边界纠正完毕,继续寻找随从...")
forward_w_count = 0
near_turn_count = 0
continue
# ==================================================
is_pawn_prompt, _, _ = check_ui_prompt(img_gray, template_pawn_interact)
if is_pawn_prompt:
print("🐞 [DEBUG] 发现 [打招呼] 提示,刹车并验证 1 秒稳定性...")
for k in ['w', 'a', 's', 'd', 'left', 'right']: pyautogui.keyUp(k)
stable = True
for _ in range(5):
time.sleep(0.2)
sct_img_v = sct.grab(monitor)
img_gray_v = cv2.cvtColor(np.array(sct_img_v), cv2.COLOR_BGRA2GRAY)
prompt_still_there, _, _ = check_ui_prompt(img_gray_v, template_pawn_interact)
if not prompt_still_there:
stable = False
break
if stable:
print("🛑 贴脸确认成功!稳定持续 1 秒。")
forward_w_count = 0
near_turn_count = 0
long_press_key("'", hold_time=1.5)
print("⏳ 等待 1 秒让对话 UI 加载...")
time.sleep(1)
tap_key("/")
is_near = False
else:
print("⚠️ [打招呼] 提示闪退!说明已在随从极近距离,进入微调靠近模式。")
is_near = True
continue
is_hire_ui = False
print("🔎 开始执行 OCR 文字扫描 (最多尝试 3 次)...")
for attempt in range(1, 4):
sct_img_hire = sct.grab(monitor)
img_bgr_hire = np.array(sct_img_hire)
h, w = img_bgr_hire.shape[:2]
roi = img_bgr_hire[int(h * 0.1):int(h * 0.9), int(w * 0):int(w * 0.3)]
ocr_results = reader.readtext(roi, detail=0)
print(f"🖨️ [OCR 调试 - 雇佣确认 第{attempt}次] 当前区域识别到: {ocr_results}")
for text in ocr_results:
text_lower = text.lower()
if any(keyword in text_lower for keyword in OCR_KEYWORDS):
is_hire_ui = True
print(f"🎯 [OCR确认] 第 {attempt} 次扫描找到关键文字: '{text}'")
break
if is_hire_ui:
break
if attempt < 3:
print("⏳ 画面可能未完全加载,0.5秒后重试...")
time.sleep(0.5)
if not is_hire_ui:
print("⚠️ 3次 OCR 均未直接进入雇佣界面,执行纠正操作:(i -> /)")
tap_key("i")
tap_key("/")
print("⏳ 纠正完成,等待 1.5 秒动画...")
time.sleep(2)
long_press_key("w", hold_time=0.5)
print("🔄 重新进入找人流程...")
print("🐞 [DEBUG] 纠正完毕,强制重启 FIND_PAWN 流程")
continue
else:
print("✅ 确认已在雇佣界面!")
print("▶️ 开始执行 雇佣")
tap_key("/")
tap_key("/")
print("▶️ 开始执行 解雇")
long_press_key("'", hold_time=3)
tap_key("/")
tap_key("k")
tap_key("/")
# ==================================================
# --- 【全新三层优先级】OCR 动态智能送礼策略 ---
# ==================================================
print("▶️ 准备执行 OCR 动态送礼策略...")
time.sleep(1.5) # 等待送礼物品列表完全展开稳定
# 定义三层优先级关键词
gift_tier1 = ["诺根", "古币"]
gift_tier2 = ["金龟", "帝王", "艳金龟", "暴击", "之箭"]
gift_ruler = ["统治者", "护甲"]
seen_ruler_armor_count = 0
found_target_gift = False
if has_gifted_ruler_armor:
print("🎯 [策略更新] 统治者护甲已送过一次,当前已升级为绝对最高优先级 (Tier 0)!")
# 遍历扫描物品(因为背包可以循环滚动,设置最大扫描30次防死循环)
for scroll_attempt in range(30):
sct_img_gift = sct.grab(monitor)
img_bgr_gift = np.array(sct_img_gift)
h_g, w_g = img_bgr_gift.shape[:2]
# 截取右侧详情面板上方的物品名称区域 (基于截图精确定位,避开左侧网格)
roi_gift = img_bgr_gift[int(h_g * 0.10):int(h_g * 0.35), int(w_g * 0.35):int(w_g * 0.65)]
ocr_results_gift = reader.readtext(roi_gift, detail=0)
current_item_text = "".join(ocr_results_gift).replace(" ", "")
print(f"🖨️ [OCR 调试 - 送礼扫描 第{scroll_attempt + 1}次] 当前选中物品: {current_item_text}")
is_tier1 = any(kw in current_item_text for kw in gift_tier1)
is_tier2 = any(kw in current_item_text for kw in gift_tier2)
is_ruler = any(kw in current_item_text for kw in gift_ruler)
# 核心优先级判断逻辑
if has_gifted_ruler_armor:
# 护甲已经被送过一次,看到护甲直接秒送
if is_ruler:
print("🎯 [送礼策略] 发现 [统治者护甲] (已升级为最高优先级),秒送!")
found_target_gift = True
break
elif is_tier1 or is_tier2:
print(f"🎯 [送礼策略] 发现备选高优资源 ({current_item_text}),准备送出!")
found_target_gift = True
break
else:
# 严格三层优先级:诺根(T1) > 金龟/暴击(T2) > 护甲(T3)
if is_tier1:
print(f"🎯 [送礼策略] 发现第一优先级资源(T1) ({current_item_text}),秒送!")
found_target_gift = True
break
elif is_tier2:
if scroll_attempt >= 5:
print(f"🎯 [送礼策略] 已寻找5次未见T1,接受第二优先级(T2) ({current_item_text})!")
found_target_gift = True
break
else:
print(f"⏩ [送礼策略] 发现T2 ({current_item_text}),但前5次优先寻找T1,暂时跳过...")
elif is_ruler:
seen_ruler_armor_count += 1
if seen_ruler_armor_count >= 2:
# 如果是第二次遇到护甲,说明已经遍历了整个背包且没发现T1和T2
print(
"🎯 [送礼策略] 背包已循环一圈,未见更高优资源。第二次遇到 [统治者护甲],执行兜底送出!")
found_target_gift = True
has_gifted_ruler_armor = True # 永久提升护甲优先级标记!
break
else:
print("⏩ [送礼策略] 第一次遇到 [统治者护甲] (最低优先级),跳过并继续寻找高优资源...")
print(" -> 不符合当前最高优策略,按 [l] 切换下一个物品...")
tap_key("l")
time.sleep(0.5) # 等待UI切换刷新
if not found_target_gift:
print("⚠️ [送礼警告] 遍历背包未找到符合策略的物品,将盲按送出当前停留的物品!")
print("▶️ 开始执行 确认送礼")
tap_key("/") # 确认选中当前已停留在界面的礼物
time.sleep(0.5)
tap_key("i")
tap_key("/")
tap_key("/")
tap_key("/")
tap_key("/")
print("✅ 随从交互与送礼流程全部完成!等待 1 秒...")
time.sleep(1.0)
print("🐞 [DEBUG] 状态切换: FIND_PAWN -> RESET_SCENE")
CURRENT_STATE = "RESET_SCENE"
continue
# ==================================================
# --- 【极限衰减版】IBVS 向量导航寻路模块 ---
# ==================================================
target_found = False
found, max_loc, _ = check_ui_prompt(img_gray, template_find_target, threshold=FIND_THRESHOLD)
if found:
target_found = True
has_seen_pawn = True
anchor_x = img.shape[1] // 2
anchor_y = img.shape[0] // 2 + 50
target_center_x = max_loc[0] + w_target / 2
target_center_y = max_loc[1] + h_target / 2
dx = target_center_x - anchor_x
dy = anchor_y - target_center_y
distance = math.hypot(dx, dy)
angle_rad = math.atan2(dx, dy)
angle_deg = math.degrees(angle_rad)
if target_found:
turn_key = None
turn_duration = 0.0
should_press_w = True
# 贴脸时放大死区,只要目标大致在正前方,就不再转头
dynamic_deadzone = 25 if (is_near or distance < 250) else 10
if angle_deg < -dynamic_deadzone:
turn_key = 'left'
elif angle_deg > dynamic_deadzone:
turn_key = 'right'
# 1. 摄像机视角补偿控制器
if turn_key:
if is_near or distance < 250:
should_press_w = False
near_turn_count += 1
# 修复乘号与减号
dampener = max(0.05, 1.0 - (near_turn_count * 0.30))
turn_duration = max(0.001, min(0.010, abs(angle_deg) * 0.0001 * dampener))
else:
turn_duration = max(0.002, min(0.020, abs(angle_deg) * 0.0002))
near_turn_count = 0
# 2. 前进控制器
if is_near or distance < 200:
move_duration = 0.02
else:
move_duration = max(0.05, min(0.20, distance * 0.0001))
if not should_press_w:
move_duration = 0.0
# ==========================================
# 极近距离/对准判定 防卡死机制
# ==========================================
if not turn_key:
forward_w_count += 1
near_turn_count = 0
else:
forward_w_count = 0
if forward_w_count > 9:
side_key = random.choice(['a', 'd'])
print(
f"⚠️ [防卡死] 连续精准前进 {forward_w_count - 1} 次未贴脸,可能被石头卡住,向 '{side_key}' 侧滑 1 秒解卡...")
pyautogui.keyDown(side_key)
time.sleep(1.0)
pyautogui.keyUp(side_key)
forward_w_count = 0
continue
# ==========================================
# Debug 面板打印
# ==========================================
print(f"🐞 [DEBUG] [向量伺服] 距离: {distance:.0f} | 夹角: {angle_deg:.1f}°")
if not should_press_w:
print(
f" -> [动作] 暂停前进,纯原地转角对准: [{turn_key}] {turn_duration:.3f}s (衰减级: {near_turn_count})")
elif turn_key:
print(f" -> [动作] 边走边转: [{turn_key}] {turn_duration:.3f}s, [w] {move_duration:.3f}s")
else:
print(f" -> [动作] 完美对准,笔直冲刺: [w] {move_duration:.3f}s (第{forward_w_count}次)")
# ==========================================
# 独立时间轴异步执行 (移动 和 转视角 不互相阻塞)
# ==========================================
if should_press_w: pyautogui.keyDown('w')
if turn_key: pyautogui.keyDown(turn_key)
total_time = max(move_duration, turn_duration)
t_start = time.time()
w_released = not should_press_w
turn_released = not bool(turn_key)
while time.time() - t_start < total_time:
elapsed = time.time() - t_start
if not turn_released and elapsed >= turn_duration:
pyautogui.keyUp(turn_key)
turn_released = True
if not w_released and elapsed >= move_duration:
pyautogui.keyUp('w')
w_released = True
time.sleep(0.001)
if not w_released: pyautogui.keyUp('w')
if not turn_released: pyautogui.keyUp(turn_key)
else:
# 丢失视野盲转
turn_duration = 0.01 if has_seen_pawn else 0.08
print(
f"🐞 [DEBUG] [找随从] 视野内未发现{TARGET_DISPLAY_NAME}名牌,原地向右盲转 ({turn_duration}s)...")
pyautogui.keyDown('right')
time.sleep(turn_duration)
pyautogui.keyUp('right')
time.sleep(0.02)
# ==================================================
# 状态 3:动态监测边界跑动重置位置 (强化分阶段绕行)
# ==================================================
elif CURRENT_STATE == "RESET_SCENE":
print("\n=====================================")
print("🐞 [DEBUG] 正在执行 RESET_SCENE 寻路重置")
hit_boundary = False
random_side_key = random.choice(['a', 's', 'd'])
# 定义两个奔跑阶段:先盲冲正前方 10秒,若失败说明撞墙/石头,再盲切侧方或后方冲 10秒
escape_phases = [
('w', 10.0, "第一阶段:向正前方"),
(random_side_key, 10.0, "第二阶段:避障向随机方向")
]
for key_to_press, max_time, phase_name in escape_phases:
if hit_boundary:
break # 如果在某个阶段已经撞到了真边界,直接跳出循环进入结算
print(f"🏃♂️ {phase_name} ({key_to_press}) 奔跑,动态检测退出提示 (最大等待 {max_time} 秒)...")
pyautogui.keyDown(key_to_press)
t_start = time.time()
while time.time() - t_start < max_time:
sct_img = sct.grab(monitor)
img_gray = cv2.cvtColor(np.array(sct_img), cv2.COLOR_BGRA2GRAY)
is_exit_prompt, _, exit_val = check_ui_prompt(img_gray, template_exit_rift, threshold=0.45)
trigger_exit = False
if is_exit_prompt:
if exit_val > 0.80:
trigger_exit = True
else:
time.sleep(0.3)
sct_img_v = sct.grab(monitor)
img_gray_v = cv2.cvtColor(np.array(sct_img_v), cv2.COLOR_BGRA2GRAY)
is_exit_prompt_2, _, _ = check_ui_prompt(img_gray_v, template_exit_rift, threshold=0.45)
if is_exit_prompt_2:
trigger_exit = True
if trigger_exit:
hit_boundary = True
print("🛑 🐞 [DEBUG] [场景重置] 成功撞到地图边界!触发退出提示,准备确认退出...")
break
time.sleep(0.05)
# 当前阶段时间耗尽或成功触发边界后,必须松开按键!
pyautogui.keyUp(key_to_press)
if not hit_boundary and key_to_press == 'w':
print("⚠️ 第一阶段 10 秒内未触碰边界,可能卡死在地形。进入下一阶段避障!")
# 如果成功摸到了边界,执行退出验证流程
if hit_boundary:
time.sleep(1.0) # 等待UI稳定
sct_img_exit = sct.grab(monitor)
img_gray_exit = cv2.cvtColor(np.array(sct_img_exit), cv2.COLOR_BGRA2GRAY)
h, w = img_gray_exit.shape
roi_y1, roi_y2 = int(h * 0.4), int(h * 0.9)
roi_x1, roi_x2 = int(w * 0.4), int(w * 0.6)
roi_gray = img_gray_exit[roi_y1:roi_y2, roi_x1:roi_x2]
ocr_results = reader.readtext(roi_gray, detail=1)
shi_y, fou_y = None, None
for bbox, text, prob in ocr_results:
if '是' in text:
shi_y = (bbox[0][1] + bbox[2][1]) / 2 + roi_y1
elif '否' in text:
fou_y = (bbox[0][1] + bbox[2][1]) / 2 + roi_y1
if shi_y is not None and fou_y is not None:
for _ in range(5):
sct_img_check = sct.grab(monitor)
img_gray_check = cv2.cvtColor(np.array(sct_img_check), cv2.COLOR_BGRA2GRAY)
band_w = int(w * 0.3)
# 已安全修复边界退出的运算符
x_start = int(w / 2 - band_w / 2)
x_end = int(w / 2 + band_w / 2)
shi_band = img_gray_check[int(shi_y) - 10:int(shi_y) + 10, x_start:x_end]
fou_band = img_gray_check[int(fou_y) - 10:int(fou_y) + 10, x_start:x_end]
shi_lum = np.mean(shi_band)
fou_lum = np.mean(fou_band)
print(f" -> [亮度检测] '是'亮度: {shi_lum:.1f} | '否'亮度: {fou_lum:.1f}")
# 这里的目标是退出,所以需要确认 '是' 被泛白选中
if shi_lum > fou_lum + 5:
print(" ✅ '是' 区域已泛白选中,按下 [/] 确认退出异界石...")
tap_key("/")
time.sleep(3.0)
break
else:
print(" 🔄 '是' 未被选中,按下 [k] 切换选项...")
tap_key("k")
time.sleep(0.5)
else:
print(" ⚠️ 退出纠正循环超时结束,执行保底确认 [/]...")
tap_key("/")
time.sleep(3.0)
else:
print(" ⚠️ OCR未能同时定位'是/否',执行盲按确认 [/]...")
tap_key("/")
time.sleep(3.0)
else:
print("⚠️ 两阶段长跑共 20 秒仍未碰到边界?执行保底重置盲按...")
tap_key("/")
time.sleep(3.0)
current_loop += 1
print(f"🏆 一次完整循环结束!当前已完成: {current_loop} 次")
if MAX_LOOPS > 0 and current_loop >= MAX_LOOPS:
print(f"🎉 已达到设定的最大循环次数 ({MAX_LOOPS}),自动挂机结束!")
sys.exit()
else:
print("🐞 [DEBUG] 状态切换: RESET_SCENE -> FIND_RIFTSTONE")
CURRENT_STATE = "FIND_RIFTSTONE"
is_near = False
has_dashed = False
forward_w_count = 0
near_turn_count = 0
if __name__ == '__main__':
start_tracking()