背景

florr-auto-pathing 是一个开源的 florr.io 自动寻路/刷怪脚本(GitHub 上游地址,GPL-3.0)。它写得其实挺完整:GUI 调度、专用 Chrome 实例、小地图像素定位、A* 寻路、卡住脱困、死亡重开,一应俱全。

但我在自己机器上跑的时候,它基本没法用。两个症状看起来毫不相关,最后发现是同一个原因:florr 更新后改了两个颜色,而脚本里把这些颜色写死了。

⚠️ 提前说明:自动化操作违反 florr.io 服务条款,有封号风险,请自行判断要不要用。本文只聊代码和像素调试。


症状一:一直刷「无法检测玩家位置」

日志里无限重复这一行:

⚠️ 无法检测玩家位置,持续重试中 (第1次)
⚠️ 无法检测玩家位置,持续重试中 (第2次)
...

排查:先排掉"分辨率"这个嫌疑人

脚本定位玩家的方式是:截屏幕右上角一块固定区域,在里面找小地图上的玩家标记。它所有坐标常量都是照 1920×1080 量出来的,所以我先怀疑分辨率/缩放不对。

实测两种配置下代码算出来的截图区:

  • 2560×1600 + 缩放 150% → [2086, 30, 444, 444]
  • 1920×1080 + 缩放 100% → [1600, 20, 300, 300]

两种情况下这块区域都正好框住了小地图(拿小地图模板做匹配,得分 0.966)。也就是说位置没错,但区域里就是找不到标记。

于是把这块区域的像素摊开,逐个色块打印颜色和形状:

玩家标记实际渲染颜色: #f4e476 / #f6e578    (R244 G228 B118)
代码要求的颜色:       #f8de60             (R248 G222 B 96)
判定方式:             ±20 容差 → B 通道允许 76~116

B 通道 118 对上限 116 —— 只差 2。

结论很清楚:标记一直都在小地图上,只是它渲染出来的颜色比脚本标定的值"淡"了 2~4 个色阶,于是整个色块被当成噪声丢掉。偶尔某一帧抗锯齿混合后 B 掉进 116 以内,就"侥幸命中"一次——所以现象是时好时坏,而不是一直失败。

改法

# utils.py
_MARKER_COLOR_TOLERANCE = 35   # 原来是写死的 20

def get_player_location_on_map(opencv_img, target_color, map, precise=False):
    target_color_bgr = tuple(int(target_color[i:i+2], 16) for i in (4, 2, 0))
    _tol = _MARKER_COLOR_TOLERANCE
    lower_bound = np.array([max(0, c - _tol) for c in target_color_bgr])
    upper_bound = np.array([min(255, c + _tol) for c in target_color_bgr])

效果(同一块区域、同一帧对比)

  • 容差 ±20(原版):命中 1 个像素、0 个合格色块 → 返回 None,继续刷"无法检测"
  • 容差 ±25:21 个像素、色块半径 2.55、位置稳定 → 正常
  • 容差 ±35(改后):22 个像素、色块半径 2.55、位置稳定 → 正常

放宽到 35 也不会误吞小地图里其它元素:白色 #fbfaf4、深色 #201f1a、绿色花丛 #b0ec80 的 R 通道都差得很远。


症状二:死了不点「继续」,到菜单点不了「开始」

这两个其实是同一个 bug 的两面。脚本的工作流程是全自动的:

死亡 → 点「继续」→ 回到开局菜单 → 选生态区 → 点「开始」→ 重新进场

而这两个按钮的判定方式,同样是写死的颜色:

_BUTTON_GREEN_RGB = (27, 203, 37)   # 采样按钮位置,算这个颜色的像素占比
# 死亡页「继续」按钮要求绿色占比 > 0.15;开局页「开始」按钮 > 0.10

我把屏幕上所有"绿色系 + 按钮形状"的色块全扫出来,实测结果是:

死亡结算页的「继续」按钮

  • 代码标的点:(1279, 939)
  • 实测按钮框:(1218, 907) 大小 125×69,中心 (1279, 941)
  • 实测填充色:#7eb35a

开局菜单的「开始」按钮

  • 代码标的点:(1412, 781)
  • 实测按钮框:(1372, 739) 大小 175×84,中心 (1463, 780)
  • 实测平均色:#7ab94e

位置全对(代码标的点甚至就落在按钮框内部),颜色全错:#7eb35a 的 R 通道是 126,而 (27,203,37) ± 25 要求 R ≤ 52。

于是三个按钮(继续 / 开始 / 以游客身份游玩)的判定同时恒为 0。链条就变成了:

死了点不了「继续」→ 死人在小地图上没有标记 → 又回到"无法检测玩家位置"的死循环里出不来。

两个看起来毫不相干的症状,其实是同一个根因。

改法:不再依赖精确 RGB,改用「色相 + 形状」

_BUTTON_HUE_RANGE = (35, 85)       # OpenCV 色相
_BUTTON_MIN_SAT = 70               # 饱和度下限
_BUTTON_MIN_FILL = 0.45            # 色块占外接矩形的比例(按钮是实心的)
_BUTTON_ASPECT_RANGE = (1.2, 8.0)  # 按钮是扁的
_BUTTON_MAX_DIST_REF = 120         # 色块中心离预期位置的容忍距离
_BUTTON_SCAN_REF = (760, 440, 1160, 700)   # 三个按钮位置的包围盒,只扫这一块

判定逻辑是四条同时满足才认:

  1. 是绿色系(色相 35~85、饱和度 ≥70)—— 饱和绿、柔和绿都能过,换配色也不失效
  2. 形状像按钮(宽高比 1.2~8、填充率 ≥0.45、尺寸有下限)
  3. 离预期位置近(参照分辨率换算后 120px 以内)
  4. 归属唯一 —— 只接受"离本按钮比离另外两个按钮都近"的色块

第 4 条是被坑出来的:开局菜单上有一块绿离"继续"的位置只有 74px,光按"离谁近"会把两个页面搞混。所以又补了一条规则:屏幕上看得见「开始」按钮时,直接判定"这不是死亡页面",绝不误点「继续」。

另外,点击动作也改成点实际找到的色块中心(找不到才退回写死的坐标),这样以后按钮再挪几十像素也点得中。

关键一点:老的精确颜色判定原样保留在最前面当快路径,只加不删——对"按钮没变色"的环境,行为完全不变,不引入回归。


验证:不靠感觉,靠数据

① 按钮点击落点(现场盯屏记录)

18:06:25 点 CONTINUE (1280,940) → 18:06:26 回到开局菜单 → 点 START (1463,780) → 18:06:32 进场
18:07:32 点 CONTINUE (1279,941) → 18:07:33 回到菜单   → 点 START (1463,780) → 18:07:36 进场
18:08:09 点 CONTINUE (1279,941) → 18:08:10 回到菜单   → 点 START (1463,780) → 18:08:15 进场

注意「开始」的落点是 (1463,780) —— 正是新逻辑算出来的按钮真实中心(老代码写死的是 (1412,781)),这说明补丁确实在生效。死亡 → 复活 → 重开,每轮 5~8 秒,全程自动。

② 玩家标记

修好后连测 12/12 命中;连续采样位置平滑移动 (160,164) → (160,149),说明角色真的在朝目标点走,不再满地图乱跳。

③ 长时间运行

之后连续 2.5 分钟以上存活,一路从地图南部走到北部,到位后原地索敌输出。

④ 回归测试

py_compile 通过;test_utils.py + test_main_worker.py 共 77 passed;另外在一个全新 venv(照 requirements.txt 从零装依赖)里跑 187 passed。


怎么用

  • 已经有源码:直接用改好的 utils.py覆盖同名文件即可,不用改别的任何文件

  • 完整包(含地图模板、说明文档、一键启动脚本):链接

  • 环境:Windows + Chrome;Python 3.12 或更低(依赖锁了 numpy<2,3.13 装不上);游戏窗口必须全屏;分辨率推荐 1920×1080 + 缩放 100%(实测 2560×1600 + 缩放 150% 也可以,唯一要避开的是 1920×1080 + 缩放 150%)


写在最后

这两个 bug 有个很典型的共同点:把"渲染出来的颜色"当成"稳定的常量"写进了代码。游戏一改配色,代码不会报错、不会崩溃,只会安静地失效——然后表现为看起来毫不相干的业务症状("找不到玩家"、"点了没反应")。

调试上真正管用的也就两件事:

  1. 把那一小块像素摊开来看——颜色、尺寸、形状、占比,全部打印出来,结论自己会浮出来
  2. 别急着改,先做对照——同一帧下 ±20 / ±25 / ±35 的结果并排摆着,谁对谁错就没有争议了

上游是 GPL-3.0,转发请保留 LICENSE。最后再提醒一次:自动化违反 florr.io 服务条款,有封号风险,自己判断。