# AutoStore Bot Minecraft 自动扩地机器人 —— 由原 Fabric 客户端模组 [AutoStore](https://git.bgjq.top/Lyizai/AutoStore) 重构而来: **Node.js + mineflayer 实现,UI 从游戏内 GUI 改为网页控制台,并针对装有 Grim 反作弊的服务器做合规适配。** 不需要开游戏客户端,一条命令启动,浏览器打开控制台即可开始自动扩地。 支持**同时挂多个玩家(机器人)**、**开拓日志**、**游戏公屏完整日志**与**爬取服务器地图的大地图**。 ``` npm install cp .env.example .env # 填入服务器地址与账号密码(支持多个账号) npm start # 打开 http://127.0.0.1:3081 ``` --- ## 功能对照 | 原模组功能 | 新版实现 | 说明 | | --- | --- | --- | | 远程拉取 `markers.json` 领土数据 | ✅ | 带时间戳缓存击穿 + `no-cache` 头,实测 24 个邦国 / 11.5 万区块 200ms 解析完成 | | 四种扩地策略 | ✅ | 方形外扩 / 区域 S 形扫描 / 玩家周围 / 绕国一圈 | | 区域扩地严格 S 形扫描 | ✅ | 起始边取离王城最近的边,由内而外蛇形推进 | | 飞地(疏漏区块)检测与优先填补 | ✅ | 按 dynmap 奇偶填充规则解析多边形,正确识别国中孔洞 | | 自动寻路前往目标区块 | ✅ | Baritone → mineflayer-pathfinder,高度偏好启发式等价移植 | | 寻路失败重试 / 往边上走两步 / 连续超时回城 | ✅ | 状态机与阈值完全一致 | | 运行时接壤校验、目标有效性校验 | ✅ | 每 tick 校验,避免在无效区块上白费时间 | | 现实时间限制(默认 11:00-22:00) | ✅ | 改为可配置时段,并支持「到点自动开始」 | | 监听字幕 / 聊天判定开垦结果 | ✅ | 兼容 title / subtitle / actionBar / Boss 条四条通道 | | 自动进食 | ✅ | | | 自动攻击敌对生物(豁免中立生物) | ✅ | 铁傀儡 / 僵尸猪灵永不攻击;末影人 / 蜘蛛默认不打(见下方差异说明) | | 耐久保护回城 | ✅ | | | 自动游泳防溺水 | ✅ | 水中持续按住跳跃键上浮 | | 自动用船过水 | ⚠️ 默认关闭 | mineflayer 无载具物理,Grim 风险高,详见「行为差异」 | | 实时地图 GUI | ✅ 网页版 | Canvas 绘制,支持缩放 / 平移 / 图层开关 | | 日志导出 | ✅ | 网页按钮导出到 `data/` | | SimpCore 开放 API 第四数据源 | ✅ | 低频轮询 + 手动全服拉取 | | Grim 反作弊适配 | ✅ 新增 | 见下方专章 | | **多机器人同时挂机** | ✅ 新增 | 一个进程挂多个账号,各自独立连接 / 登录 / 扩地,配置可独立覆盖 | | **开拓日志** | ✅ 新增 | 逐条记录开拓玩家、邦国、区块、方块坐标、时间、判定来源,落盘可导出 CSV | | **游戏公屏完整日志 + 聊天框** | ✅ 新增 | 全部公屏 / 系统消息按机器人分文件留档,控制台可直接发公屏消息 | | **大地图** | ✅ 新增 | 爬取服务器地图站点瓦片与领土标记,一张图上看全部机器人坐标 | | **登录验证门禁** | ✅ 新增 | 游戏内登录 + 控制台登录双重校验,通过后才允许操作机器人与发送消息 | --- ## 多机器人同时挂机 ### 配置 在 `.env` 里一次配好所有账号(**密码只从这里读,绝不落代码与配置文件**): ```bash # 写法一:用户名:密码,逗号分隔 MC_BOTS=开拓一号:password1,开拓二号:password2 # 写法二:JSON 数组(用户名/密码含逗号冒号时用这个) MC_BOTS=[{"username":"bot1","password":"p1"},{"username":"bot2","password":"p2","label":"二号机"}] # 不设置 MC_BOTS 时回退到单机器人 MC_USERNAME=AutoStoreBot MC_PASSWORD=xxxxxx ``` 启动后控制台左栏会列出全部机器人(连接状态 / 坐标 / 任务状态 / 已开拓数), 顶栏下拉框切换当前操作的机器人。 ### 每个机器人独立的配置 控制台右侧配置表单勾选「**仅对当前机器人生效**」后保存,即为该机器人写入专属配置覆盖 (存 `data/bots.json`,**不含密码**);不勾选则写入全局默认配置。未覆盖的字段一律回退到全局默认。 典型用法:给每个账号指定不同的 `目标邦国` 与 `扩地策略`,让它们分工扩张不同方向。 ### 设计取舍 - **共享一份领土模型**:`markers.json` 是全服数据(约 160KB / 11.5 万区块), 没必要每个机器人各拉一遍,因此数据服务是单例、共享给全部控制器。 - **邦国归属按机器人解析**:`ExpansionController` 在 `start()` 时把识别到的邦国固化到 `controller.stateName` 并写回本次任务配置,规划器不再读全局 `selfStateName` (否则会被最后启动的机器人覆盖,导致 A 机器人按 B 的邦国规划)。 - **「会话新占领」区块是全局共享的**:控制台地图的「己方领土」显示的是最近启动的那个机器人的邦国。 --- ## 开拓日志 `src/core/expansionLog.js` 逐条记录每一次成功开拓: | 字段 | 说明 | | --- | --- | | `t` | 时间戳(毫秒),界面按 `YYYY-MM-DD HH:mm:ss` 展示 | | `bot` / `botId` | 开拓玩家(机器人游戏名 / id) | | `state` | 开拓归属的邦国 | | `cx` / `cz` | 区块坐标 | | `bx` / `bz` | 方块坐标(服务器回报的精确位置,通用成功文案时为空) | | `source` | 判定来源:`chat`(带坐标的聊天消息)/ `chat-generic`(无坐标的通用文案) | | `sessionCount` | 该机器人本轮任务的第几个 | - 落盘:`data/expansion-log.jsonl`(追加写入,超过 16MB 轮转为 `.1`) - 内存:保留最近 5000 条供实时查看,磁盘上的历史不受此限制 - 控制台「开拓日志」页签支持按机器人筛选、清屏、导出带 BOM 的 CSV(Excel 打开中文不乱码) --- ## 游戏公屏完整日志与聊天框 `src/core/chatLog.js` 把每个机器人收到的**全部**消息留档,不只留开垦相关的: - 公屏聊天(其他玩家发言,含发言人)、系统公告、动作栏消息 - 本机器人发出的聊天与指令(便于对照「指令发出后服务器回了什么」) - 落盘:`data/chat/<机器人名>.jsonl`(每机器人一份,超过 16MB 轮转) - 控制台「公屏聊天」页签可按当前机器人或全部机器人查看、按关键字过滤、导出为纯文本 控制台右下角的「控制台 / 聊天框」可以选择**公屏聊天**或**游戏指令**模式发送内容。 --- ## 大地图 `src/core/bigMapService.js` 爬取服务器地图站点(本项目部署的是 [squaremap](https://github.com/jpenilla/squaremap))的瓦片图片与领土标记, 把所有机器人画在同一张图上。 ### 坐标系(已对源站实测标定) squaremap 前端用 `L.CRS.Simple` + `tileSize: 512`,瓦片路径为 `tiles///_.png`,并且: - 瓦片图片固定 **512×512** 像素; - 最大原生层级 `zoom.max`(本站为 3)下 **1 方块 = 1 像素**; - 因此层级 `z` 每张瓦片覆盖 `512 × 2^(maxZoom − z)` 个方块, 瓦片 `(x, y)` 覆盖方块区间 `[x·B, (x+1)·B) × [y·B, (y+1)·B)`。 实测标定:本站层级 0 每张瓦片覆盖 4096 方块,全世界(±20480)为 **10×10 = 100 张**。 用 27 个王城坐标交叉验证——按上式换算,王城所在瓦片的平均海洋像素占比为 **28.7%**, 而错误假设(256 / 1024 方块每瓦片、整体偏移半张瓦片)分别为 76.0% / 47.3% / 41.6%, 即只有正确公式能让王城稳定落在陆地上。 ### 以机器人为中心 + 可调图幅 大地图的用途是看**自己机器人周边**,所以默认以机器人当前位置为中心(`bigMap.followBot`)。 界面上的「图幅」下拉框决定半径: | 图幅 | 层级 0 需要的瓦片数 | | --- | --- | | ±512 格 | 1 | | ±1024 格 | 1~4 | | ±4096 格(默认) | 9 | | ±8192 格 | 25 | | 全世界(±20480) | 100 | 层级 0 每张瓦片覆盖 4096 格,所以「只看周边」比「拉全世界」省一到两个数量级。 点「爬取周边」只会爬当前图幅内的瓦片;「回到机器人」把视图拉回机器人所在位置。 ### 缓存与爬取 | 层级 | 每瓦片方块数 | 全世界瓦片数 | 策略 | | --- | --- | --- | --- | | 0 | 4096 | 10×10 = 100 | 启动时自动预爬取(以机器人位置为中心,只爬当前图幅内) | | 1 | 2048 | 20×20 = 400 | 按需拉取 + 永久缓存 | | 2 | 1024 | 40×40 = 1600 | 按需拉取 + 永久缓存 | | 3 | 512 | 80×80 = 6400 | 按需拉取 + 永久缓存 | - 磁盘缓存:`data/mapcache///_.png`,命中即不再回源 - 瓦片由后端代理(`/api/bigmap/tile/:z/:x/:y`),避免浏览器直连地图站点的跨域问题 - 领土以 `markers.json` 的**原始多边形**绘制(预编译成 `Path2D`,按 even-odd 填充保留国中孔洞), 叠加本次会话新占领区块、王城与**全部机器人坐标(含名字标签)** - 操作:滚轮缩放、拖拽平移、图幅切换、「爬取周边」/「回到机器人」一键定位 --- ## SimpCore 开放 API `src/core/simpApiClient.js` 对接 [SimpCore 开放 API](https://open.bgjq.top/api-docs.html) (邦国崛起服务器的只读查询服务)。它既是占领判定的第四数据源,也把服务端的公开信息 完整暴露到控制台。 ### 文档里的接口基本都接了 | 分类 | 接口 | | --- | --- | | 健康 | `GET /health`(免鉴权) | | 玩家 | `/info/players`、`/info/players/{key}`、`/info/players/{key}/union` | | 邦国 | `/info/unions`、`/info/unions/{key}`、`/members`、`/territory`、`/tech`、`/contributions` | | 全服 | `/info/territory`、`/info/buffs`、`/info/diplomacy`、`/info/bans`、`/info/logs`、`/info/announcements` | 在左栏「领土数据源」点 **查看 API 数据** 打开浏览弹层,分五个页签:概览 / 邦国 / 玩家 / 全服 / 实时 V2。 限流按「Key × 接口」每分钟计算,所以**全部按需点击加载 + 会话内缓存 + 服务端 5 分钟缓存**,绝不做轮询。 ### API 与 API-V2 是两套独立的密钥 文档明确说明两者**互不通用的**: - **API**:`https://bgjq.simpfun.cn/api/` —— 邦国、领土、玩家、全服数据,配置 `apiKey`(或环境变量 `SIMP_API_KEY`) - **API-V2**:`https://bgjq.simpfun.cn/api-v2/` —— 服务器实时状态(在线玩家 / 装备 / 游戏时间 / 末影龙), 需要在密钥中心**另外申请**,配置 `apiV2Key`(或环境变量 `SIMP_API_V2_KEY`) V2 的接口级配额很小(末影龙只有 **1 次/分钟**),因此 V2 一律按需查询、不做自动轮询。 ### 几个响应字段的坑(已按实测处理) | 接口 | 坑 | 处理 | | --- | --- | --- | | 捐献 | 只有 `player_uuid`,**没有玩家名** | 先用邦国成员表补全,查不到再退到全服玩家表 | | 捐献 | `amount` 可为负 | 负数是邦国支出(如「恢复民心」消耗金锭/钻石),UI 注明 | | 公告 | `message` 是**聊天组件 JSON 字符串** | `componentToText()` 压平成纯文本 | | 王城 | 服务端可能返回 `null` | 显式判空,避免 `Number(null)` 算成 0 伪装成 (0,0) | | 封禁 / 日志 | 字段名是 `banned_player_name` / `timestamp` 等 | 按实测字段解析 | | V2 `/players` | 返回**对象** `{online, max, players[]}` 而不是数组,且没有坐标 | 按对象解析 | | V2 `/dragon` | 字段是 `dragonAlive`,没有 health / phase | 按实测字段解析 | | V2 `/time` | 服务端已算好 `time24` / `time12` / `phase` | 直接用,不再自己用 tick 换算 | | V2 `/equipment` | 物品的 `damage` 是**已损耗值** | 换算成 `remainingDurability` 再显示 | | V2 限流 | 429 带 `retryAfterSeconds` | 提示里直接写出还要等多少秒 | --- ## 登录验证门禁 「登录验证后才允许操作」在两侧都做了强制: **游戏侧**(`src/bot/botManager.js`) - `canOperate()` = 已连接 + 已进入世界 + 已完成登录验证 - 未通过时 `sendCommand` / `sendChat` 直接拒绝并记警告,`isReady()` 也不放行扩地任务 - 登录 / 注册流程自身用 `{ internal: true }` 绕过门禁 - **登录确认兜底**:部分离线服登录成功后不回明确提示,`server.loginConfirmTimeoutMs` (默认 10 秒)内既没收到成功也没收到失败提示时按「已登录」放行,避免机器人永远卡死; 收到「密码错误 / 登录失败」则标记 `loginFailed` 且不放行。设为 `0` 可禁用兜底 - **免登录指令白名单**:有些服务器的登录流程本身就是指令(`/login`、`/register`, 简幻通体系还要先 `/auth`),若把门禁一刀切会形成「要先登录才能发登录指令」的死锁。 `server.preLoginCommands`(默认 `login,l,register,reg,auth`)里列出的前缀在未验证时也放行 - **外站账号绑定(如简幻通)**:服务器要求 `/auth <验证码>` 或扫码绑定时, 密码无法自动完成,程序会输出明确的中文告警(每个连接只提示一次)而不是静默死循环。 若该服务器提供固定验证码,可在配置的「账号绑定 / 二次验证模板」(`server.authCommand`)里填写 - **失败信息可操作**:发送失败会区分「未连接」「登录被服务器拒绝」「未通过登录验证」三种情况, 而不是笼统报一句「发送失败」 **控制台侧**(`src/web/server.js`) - 配置 `WEB_PASSWORD`(或 `WEB_TOKEN`)后,页面弹出登录框,`POST /api/login` 校验通过才解锁 - 所有 `/api` 与 WebSocket 握手都要求凭证(`X-Auth-Token` 头 / `?token=` / Cookie) - `/api/session` 与 `/api/login` 本身免鉴权,否则用户无法登录 - 控制台顶栏显示登录状态,可随时退出登录 --- ## 部署到服务器 本项目已在 Debian 12 上以 pm2 常驻运行验证过,关键点: ### 1. Node.js 版本要求 ≥ 22 `mineflayer 4.39` 的 `engines` 声明为 `>=22`,用 Node 18 会在运行时出问题。 若服务器上已有别的服务依赖旧版 Node,**不要升级系统 Node**,装一份项目私有运行时即可: ```bash mkdir -p /opt/autostore-bot/runtime && cd /opt/autostore-bot/runtime curl -fsSL -o node.tar.xz https://nodejs.org/dist/v24.16.0/node-v24.16.0-linux-x64.tar.xz tar -xJf node.tar.xz && rm node.tar.xz ln -sfn /opt/autostore-bot/runtime/node-v24.16.0-linux-x64 /opt/autostore-bot/runtime/current ``` ### 2. 环境变量 对外提供控制台时,`WEB_HOST` 必须是 `0.0.0.0`,并且**必须**设置 `WEB_PASSWORD` (否则任何人访问该端口都能操作你的游戏账号): ```bash WEB_HOST=0.0.0.0 WEB_PORT=78 WEB_PASSWORD=<强密码> ``` ### 3. pm2 常驻 ```bash export PATH=/opt/autostore-bot/runtime/current/bin:$PATH npm install --omit=dev pm2 start ecosystem.config.js # interpreter 指向项目私有 Node pm2 save pm2 startup systemd -u root --hp /root # 开机自启 ``` `ecosystem.config.js` 里把 `interpreter` 显式指向私有运行时,避免 pm2 用系统 Node 启动。 ### 4. 日志分两个文件 `data/pm2-out.log` 是 stdout(info 级),`data/pm2-error.log` 是 stderr (**warn 与 error 级都在这里**)。排查问题时两个都要看; 控制台页面上的运行日志是内存环形缓冲,包含全部级别,不受这个拆分影响。 ### 5. 排障经验 | 现象 | 原因 | 处理 | | --- | --- | --- | | 任务点了「开始扩地」却说「机器人尚未就绪」 | 寻路插件没加载,`bot.pathfinder` 恒为 undefined | 已在 `createBot` 后自动 `loadPlugin`;若仍出现看日志里的插件加载错误 | | 日志被「被服务器传送」刷屏 | 账号未完成服务器验证,服务端每 tick 强推位置 | 已按 30 秒窗口聚合;看是否有「账号绑定」告警 | | 连上 120 秒后被踢,反复循环 | 服务器要求外站账号绑定(如简幻通 `/auth`) | 人工完成一次绑定;绑定后同账号即可自动登录 | | `mineflayer` 加载报错 / 语法不支持 | 系统 Node < 22 | 用项目私有运行时启动 | | 能连上、能收指令,但**走不动** | 客户端移动包被服务端拒绝(回弹) | 用控制台的「移动测试」按钮确认位移;详见下方专章 | ### 6. 「机器人走不动」的排查方法 控制台右下角有**移动测试**按钮(前 / 后 / 左 / 右 / 跳),它绕开寻路器直接按控制键 2 秒并回报真实位移。 这能一眼区分两种完全不同的故障: - **位移正常、但任务报「目标不可达」** → 是规划问题(距离太远、被地形挡住、挖/放方块被 Grim 约束禁用) - **位移 ≈ 0** → 客户端移动被服务端拒绝,属于协议 / 反作弊交互问题,改规划参数没有意义 #### 最关键的一条:mineflayer 不发 `player_input` 这是本项目在真实服务器上「怎么按方向键都不动」的根因,**已在 `GrimCompat` 里自动补发**: 1.21.2+ 服务端的移动预测依赖 `player_input` 包,原版客户端每 tick 都发; 而 mineflayer 只在骑乘 / 跳下 / 潜行时发,地面移动的 forward / jump / sprint **完全不发**。 带反作弊的服务器因此判定「没有任何输入却发生了位移」,**每 tick 把玩家拉回原位置**。 实测对比(同一账号、同一位置、同一时段): | | 入站强制位置同步 | 按方向键 2 秒的位移 | | --- | --- | --- | | 不补 `player_input` | ~17 次/秒 | 0.00 格 | | 补 `player_input` + `tick_end` | **0 次** | 0.53 ~ 1.40 格 | 同时补发的还有 `tick_end`(原版 1.21.2+ 客户端每 tick 末尾发送,反作弊用它判定 tick 边界)。 两者可在配置里关掉(`grim.sendPlayerInput` / `grim.sendTickEnd`),但**线上服务器不要关**。 #### 长距离寻路会被自动拆段 mineflayer-pathfinder 只能规划**已加载区块**范围(实测本服约 8 区块 ≈ 130 格), 而扩地目标常在几十个区块之外,A* 会瞬间返回 noPath。 寻路服务会把旅程拆成若干段:走到已加载范围边缘附近 → 新区块加载 → 再规划下一段。 段长由 `bot.blockAt` 对未加载区块返回 null 的特性二分探测得出,不依赖世界内部结构。 相关配置:`pathing.maxSegmentBlocks`、`pathing.segmentMarginBlocks`。 #### 配套的诊断数据 在 `GET /api/bots/:id/status`: | 字段 | 含义 | | --- | --- | | `packetStats.inboundPosition` | 服务端下发的强制位置同步次数(持续很高 = 被回弹) | | `packetStats.outboundPosition` | 客户端上报的位置包次数 | | `packetStats.playerInput` / `tickEnd` | 每 tick 补发的协议包次数(应约等于 20/秒) | | `packetStats.onGroundFixed` | 出站包中 onGround 被世界数据纠正的次数 | | `onGround` | 客户端当前认为自己在不在空中 | | `gameMode` / `dimension` | 游戏模式与维度(adventure / spectator 会限制移动) | | `world.blockBelow` / `world.surroundings.exits` | 脚下方块与各方向「脚部 / 头部 / 地面」方块名 + 可通行判定 | | `grim.protocolPackets` | 协议补包是否生效及发送计数 | 判读方式: - 入站高、出站为 0 → 客户端根本没上报位置 - 出入都高但位移为 0 → 客户端在报,服务端不认(回弹) - `onGround` 恒为 false 而 `world.blockBelowSolid` 为 true → 开启 `grim.reportOnGroundFromWorld` - **寻路日志里「已探索节点数」极小(如几十个)就 noPath** → 不是距离问题,是移动生成器 从起点就找不到分支:检查 `world.surroundings.exits` 的 `walkable`,或把机器人挪到开阔地 --- ## Grim 反作弊适配 这是本次重构最核心的工程问题。mineflayer 是第三方协议实现,**它的物理引擎(prismarine-physics) 与行为习惯和原版客户端存在偏差,直接跑在 Grim 服务器上会被 Prediction / Reach / NoSlow / MultiActions 等检查标记**。本项目用一层 `GrimCompat` 合规层把所有对外行为收口: ### 1. 旋转绝不超前(防 Prediction) mineflayer 的 `bot.look()` 会**立刻**把 `bot.entity.yaw` 改到目标值,而朝向数据包是按 `physics.yawSpeed`(约 8.6°/tick)逐 tick 补发的。这会造成: ``` 本地已经朝新方向走了 ← 但服务端还以为你朝旧方向 ``` Grim 的 Prediction 检查正是逐 tick 复算玩家位置并与上报位置比对,这种分歧必然被标记。 **做法**:接管 `bot.look` / `bot.lookAt`,只记录目标朝向,在每个 `physicsTick` 里把 `bot.entity.yaw/pitch` 按 ≤ 8.6° 的步长推进。这样「本地朝向」永远等于「服务端已收到的朝向」, 最多差 1 tick。`awaitRotationSettled()` 可等待旋转真正同步完成。 ### 2. 攻击三重校验(防 Reach / LineOfSight / Post) `attackEntity()` 出手前依次确认: - **朝向已同步** —— 服务端认为你正看着目标(防 Post 检查) - **眼睛到目标碰撞箱最近点 ≤ 3.0 格** —— 与生存模式实体交互距离一致 - **视线可达** —— 射线检测中间没有实心方块遮挡 - **攻击冷却已好** —— 按手持武器攻速计算(剑 625ms / 斧 1000-1250ms / 空手 250ms) - **不在骑乘 / 进食 / 使用物品状态** ### 3. 动作互斥(防 MultiActions) 原版客户端不可能出现的组合一律拦下,并计入「已拦下动作」计数供控制台观察: - 疾跑 + 潜行 - 疾跑 + 使用物品(进食 / 拉弓 / 举盾) - 疾跑 + 跳跃(**默认放行** —— 这是原版合法动作,Grim 预测支持;实测被标记时可开启 `grim.blockSprintJump` 退化为普通跳跃) ### 4. 物理参数只读校验(防「改物理常数绕过预测」) `audit()` 会核对 `bot.physics` 的 `gravity` / `playerSpeed` / `sprintSpeed` / `stepHeight` / `airborneInertia` / `waterInertia` 是否仍为原版值,并检查 `physicsEnabled`、游戏模式、鞘翅飞行状态。 任何一项异常都会在控制台的「Grim 合规」面板里报警。**本项目从不修改物理常量,也从不直接写坐标。** ### 5. 寻路约束 | 约束 | 默认 | 原因 | | --- | --- | --- | | `canDig` | 关闭 | Grim 的 FastBreak 检查极严 | | 放置方块(`scafoldingBlocks`) | 关闭 | Grim 的 place 类检查极严 | | `allowParkour` | 关闭 | mineflayer 起跳时机与原版有偏差 | | `allow1by1towers` | 关闭 | 搭塔过程几乎必被标记 | | `maxDropDown` | 3 格 | 避免长距离坠落 | | `liquidCost` | 200(avoid 模式) | mineflayer 液体物理与原版差异最大,优先绕开水域 | | `allowFreeMotion` | 关闭 | 禁止自由运动 | --- ## 与原模组的行为差异(重要) 重构过程中有几处**有意为之**的取舍,请务必了解: ### 1. 自动用船默认关闭 mineflayer 使用的 prismarine-physics **没有实现载具物理**。上船后 mineflayer 会关闭本地物理预测 (`bot.on('mount') → shouldUsePhysics = false`),机器人位置完全由服务端下发的船实体位置决定, 只能靠 `bot.moveVehicle()` 驱动船前进。这意味着无法本地预测船的轨迹,Grim 的 Vehicle 类检查 逐 tick 复算载具运动时极易分歧。 因此 `waterMode` 默认为 `avoid`(绕开水域),`autoBoat` 默认为 `false`。 用船逻辑已完整实现(扫描水域 → 装备船 → 放船 → 登船 → 航行 → 下船 → 打掉回收), **仅在你实测确认该服务器对船宽容时才建议开启**(`waterMode: 'boat'` + `autoBoat: true`)。 ### 2. 末影人与蜘蛛默认不打 原模组按原版 AI 条件判断(末影人仅被激怒时、蜘蛛仅黑暗中才反击)。mineflayer 拿不到可靠的 「是否被激怒」与「环境亮度」信息,因此默认**一律不打**(宁可漏打也不误打)。 需要时可用 `combat.attackEnderman` / `combat.attackSpiders` 放开。 ### 3. 新增高危生物跳过 监守者 / 末影龙 / 凋灵默认跳过(`combat.avoidDangerousMobs`)—— 原模组会攻击它们, 但那基本等于自杀。 ### 4. 开垦时段可配置 原模组硬编码 11:00-22:00,现改为 `enforceClaimTime` + `claimStartHour` / `claimEndHour`。 ### 5. 模糊匹配算法修正 原 Java 版的 `containsFuzzy` 只取固定长度前缀的末行距离,导致 `containsFuzzy('开垦中 50%', '开垦中', 0)` 返回 `false`(连完全相同的子串都匹配不上)。本版按注释所述的语义修正为 「存在编辑距离 ≤ maxEdits 的子串」,并补了回归测试。 --- ## 安装与运行 ### 依赖 - Node.js 18+(开发与验证使用 Node 24) - 无需 Java、无需游戏客户端 ### 步骤 ```bash npm install cp .env.example .env # 编辑 .env 填入服务器地址、账号、密码 npm start ``` 启动后控制台会打印访问地址(默认 `http://127.0.0.1:3081`),浏览器打开即可。 ### 环境变量 所有敏感信息只放在 `.env`(已被 `.gitignore` 忽略,**绝不要提交**): | 变量 | 说明 | | --- | --- | | `MC_HOST` / `MC_PORT` | 服务器地址与端口 | | `MC_VERSION` | 协议版本,默认 `1.21.4` | | `MC_BOTS` | **多机器人**账号列表:`用户名:密码` 逗号分隔,或 JSON 数组写法 | | `MC_USERNAME` / `MC_PASSWORD` | 单机器人兜底(未设置 `MC_BOTS` 时生效) | | `MC_AUTH` | `offline`(离线服)或 `microsoft`(正版) | | `WEB_HOST` / `WEB_PORT` | 控制台监听地址与端口 | | `WEB_TOKEN` | 控制台令牌;非本机访问**必须**设置(页面静态资源不校验,`/api` 与 WebSocket 握手校验) | | `WEB_PASSWORD` | 控制台登录密码:配置后页面弹出登录框,验证通过才允许操作机器人的控制台 / 聊天框 | | `DYNMAP_BASE` / `DYNMAP_WORLD` | 地图站点根地址与世界目录名(领土数据 + 大地图瓦片) | | `SIMP_API_KEY` | SimpCore 开放 API Key(可选) | --- ## 网页控制台 打开控制台后: - **顶栏**:机器人下拉选择、连接 / 任务徽标、控制台登录状态、开始扩地 / 停止 / 重连 - **左栏**:机器人列表、连接、任务、**定时消息**、领土数据源、Grim 合规 - **中栏**: - 地图页签 —— **大地图**(瓦片 + 领土多边形 + 全部机器人坐标)与 **领土地图**(区块级) - 日志页签 —— **运行日志** / **公屏聊天** / **开拓日志** - **右栏**:控制台(统一输入框 + 快捷发送 + 移动测试)+ 8 个标签页的配置表单 ### 控制台输入框:聊天与指令是同一个入口 不需要切换「聊天 / 指令」模式 —— **以 `/` 开头就按游戏指令发送,否则按公屏消息发送**。 服务端 `/api/bots/:id/chat` 就是这么分流的,返回里带 `kind` 字段说明实际走的是哪条路。 另外提供: - **快捷发送**:`/u return`、`/u claimmode`、`/u restore confirm`、`/spawn`、`/list` 一键发出 - **移动测试**:前 / 后 / 左 / 右 / 跳,绕开寻路器直接按控制键 2 秒并回报真实位移 ### 响应式与移动端 | 宽度 | 布局 | | --- | --- | | ≥1200px | 三栏(状态 / 地图日志 / 配置) | | ≤1200px | 两栏,配置栏落到下方并自动分列 | | ≤820px | 单栏 + 底部导航(状态 / 地图 / 日志 / 配置一键定位) | 移动端额外处理:触控目标放大到 ≥36px、地图与日志高度按视口自适应、 避让底部安全区(`env(safe-area-inset-bottom)`)、横屏时压矮地图高度、 尊重系统「减少动效」偏好。 ### 定时重复发送消息 挂机时常需要周期性发同一条指令(例如每 300 秒 `/u restore confirm` 维持疆土民心)。 左栏「定时消息」卡片里点「+ 新增」,填内容与间隔即可;以 `/` 开头按游戏指令发送,否则按公屏消息发送。 实现上刻意**不用**朴素的 `setInterval`: - 只在「已连接 + 已通过登录验证」时才发送; - 未就绪的周期直接顺延,**不补发** —— 否则重连后会把漏掉的几条一次性连发出去; - 与指令节流队列共用,不会绕过 `server.commandCooldownMs` 变成刷屏; - 间隔下限 5 秒、单个机器人最多 20 条,防止误配置把账号玩进黑名单。 --- --- ## REST / WebSocket 接口 | 方法 | 路径 | 说明 | | --- | --- | --- | | GET | `/api/session` | 控制台登录状态(免鉴权,用于决定是否弹登录框) | | POST | `/api/login` | 控制台登录(免鉴权) | | GET | `/api/status` | 机器人池 / 任务 / 数据源 / 日志 / 大地图完整状态 | | GET | `/api/bots` | 全部机器人状态列表 | | GET | `/api/bots/:id/status` | 单个机器人状态 | | GET | `/api/bots/:id/settings` | 单个机器人的设置与生效配置 | | POST | `/api/bots/:id/settings` | 保存备注名 / 启用 / 自动开始 / 配置覆盖 | | GET | `/api/bots/:id/schedules` | 该机器人的定时消息列表(含距下次发送的秒数) | | POST | `/api/bots/:id/schedules` | 整体替换该机器人的定时消息列表 | | POST | `/api/bots/:id/task/start` | 启动该机器人的扩地(body 为配置覆盖) | | POST | `/api/bots/:id/task/stop` | 停止该机器人的扩地 | | POST | `/api/bots/:id/reconnect` | 重连该机器人 | | POST | `/api/bots/:id/move` | 手动移动测试(`direction` / `seconds`),返回真实位移 | | POST | `/api/bots` | 控制台新增玩家(`username` / `password` / `label`) | | DELETE | `/api/bots/:id` | 移除控制台新增的玩家(`.env` 里的会返回 400) | | POST | `/api/bots/:id/chat` | 发送内容:**以 `/` 开头按指令发送,否则按公屏消息发送**(要求已登录验证) | | POST | `/api/bots/:id/command` | 同上,但强制按指令发送(等价于给内容加 `/` 前缀) | | GET | `/api/bots/:id/chat` | 该机器人的公屏日志(`limit` / `since` / `kind` / `keyword`) | | POST | `/api/bots/:id/chat/export` | 导出该机器人的公屏日志到 `data/` | | GET | `/api/expansion-log` | 开拓日志(`limit` / `botId` / `state` / `since`)+ 统计 | | POST | `/api/expansion-log/export` | 导出开拓日志 CSV 到 `data/` | | POST | `/api/expansion-log/clear` | 清空内存中的开拓日志(磁盘保留) | | GET | `/api/bigmap` | 大地图元信息(坐标系、图幅、瓦片范围、缓存进度、机器人坐标、领土标记);支持 `rangeBlocks` / `bot` 参数 | | GET | `/api/bigmap/markers` | 大地图领土标记(多边形 + 王城) | | GET | `/api/bigmap/tile/:z/:x/:y` | 瓦片代理(缓存优先,避免跨域) | | POST | `/api/bigmap/crawl` | 爬取瓦片;默认爬机器人周边,支持 `centerX` / `centerZ` / `rangeBlocks` | | POST | `/api/bigmap/cache/clear` | 清空瓦片缓存 | | GET | `/api/config` | 全局配置 | | POST | `/api/config` | 保存并立即应用全局配置 | | GET | `/api/map?full=1&bot=` | 领土地图数据(`full=1` 附带全部邦国区块,`bot` 指定视角机器人) | | GET | `/api/logs` | 运行日志 | | POST | `/api/logs/export` | 导出运行日志到 `data/` | | POST | `/api/map/refresh` | 手动刷新地图数据 | | POST | `/api/api/fetch-all` | 手动拉取全服领土(重接口) | | POST | `/api/task/start`、`/api/task/stop`、`/api/command`、`/api/bot/reconnect` | 默认机器人的等价别名(兼容单机器人用法) | | WS | `/ws` | 实时推送:`status`(1s)、`log`、`chat`、`expansion` | --- ## 配置项 配置保存在 `data/autostore.json`(全局默认),可通过控制台表单修改;勾选「仅对当前机器人生效」时 写入 `data/bots.json` 作为该机器人的配置覆盖。完整字段见 [`src/config/expansionConfig.js`](src/config/expansionConfig.js),分组如下: | 分组 | 内容 | | --- | --- | | 策略 | 扩地策略、目标邦国、目标数量、扩圈上限、区域矩形 | | 超时 | 寻路 / 等待超时、重试次数、回城阈值、走两步轮数、开垦时段 | | 寻路 | 疾跑 / 游泳 / 跳跃 / 挖方块 / 放方块、高度偏好、跑酷、搭塔、下落高度、液体代价 | | 生存 | 自动进食、自动攻击、耐久保护、战斗筛选、水域策略、自动用船 | | 数据 | 地图刷新间隔、SimpCore API | | 大地图 | 开关、世界名、预爬取层级、最大层级、**图幅半径、是否跟随机器人**、启动预爬取、下载并发数 | | Grim | 合规层全部阈值 + 协议补包开关(见上) | | 服务器 | 开垦模式指令、登录 / 注册模板、登录确认超时、指令节流、重连 | --- ## 开发 ```bash npm test # 运行测试(Node 内置 test runner,无需额外依赖) npm run check # 语法自检(require 全部源码,捕获语法与加载错误) npm run dev # 文件变更自动重启 ``` ### 测试覆盖 242 个测试用例,覆盖: - 区块坐标编解码(含负数与边界) - 领土模型:飞地识别、会话占领、按邦国名取飞地与己方区块、API 数据合并、多边形保留 - 规划器:四种策略、S 形顺序、**接壤只算己方领土**(历史 bug 回归)、他国区块切断连通、 按 `config.stateName` 解析己方邦国 - markers.json 解析:奇偶填充孔洞、王城换算、颜色解析、多边形扁平化 - 字幕 / 聊天解析:进度、带坐标成功、通用成功、模糊匹配 - Grim 合规层:旋转限速、动作互斥、攻击冷却表、距离 / 视线校验、物理自检 - 战斗筛选:中立生物豁免、条件敌对、猪灵金甲判定、耐久保护 - 寻路服务:Grim 约束是否真正施加到 Movements、高度偏好目标 - 控制器状态机:完整扩地流程、超时重试、走两步、回城、耐久保护停止、完成、 多机器人邦国归属隔离、开拓日志写入与去重 - 多机器人:凭据解析(JSON / 逗号分隔 / 兜底)、机器人池组装、配置覆盖与持久化、 密码不落配置文件、未登录时拒绝发送、全局配置下发保留各自覆盖 - 机器人登录门禁:就绪判定、登录成功 / 失败 / 已登录提示、注册与登录自动补发、 确认兜底超时、公屏消息类型映射 - 开拓日志:字段记录、筛选、统计、JSONL 落盘与重启恢复、损坏行容错、环形缓冲、CSV 导出 - 公屏日志:按机器人隔离、类型 / 关键字过滤、分文件落盘、文件名安全化、文本导出 - 大地图:坐标系换算(含对源站实测标定的王城用例)、**以机器人为中心的图幅换算**、瓦片范围、缓存命中、批量爬取、缓存清理 - 定时消息:内容归一化与间隔钳制、到点发送与按周期重复、未就绪时顺延不补发、多任务独立计时、整体替换重置计时 - Web 控制台:地图负载、单 / 多机器人接口、聊天与开拓日志接口、定时任务接口、 图幅与中心计算、瓦片代理、令牌与登录鉴权、WebSocket 握手鉴权 ### 目录结构 ``` src/ ├── index.js 入口:装配模块、启动 Web 控制台、驱动多机器人 tick ├── config/ │ ├── expansionConfig.js 配置默认值 / 校验 / 归一化 │ ├── configStore.js 全局配置 JSON 持久化 │ ├── botsConfig.js 从 .env 解析多机器人凭据(密码只在这里读) │ └── botStore.js 机器人设置持久化(不含密码) ├── core/ │ ├── chunkPos.js 区块坐标编解码(整数键,零临时对象) │ ├── territoryModel.js 领土模型:区块集合、飞地、会话占领、API 合并、多边形 │ ├── territoryDataService.js markers.json 拉取与解析 │ ├── simpApiClient.js SimpCore 开放 API 客户端 │ ├── expansionPlanner.js 四种扩地策略 │ ├── subtitleChatMonitor.js 字幕 / 聊天解析与模糊匹配 │ ├── expansionLog.js ★ 开拓日志(玩家 / 邦国 / 区块 / 时间 / 来源) │ ├── chatLog.js ★ 游戏公屏完整日志(按机器人分文件) │ └── bigMapService.js ★ 大地图瓦片爬取与缓存 ├── bot/ │ ├── botPool.js ★ 机器人池:同时挂多个玩家 │ ├── botManager.js 连接、登录验证、公屏留档、指令 / 聊天发送 │ ├── scheduledMessenger.js ★ 定时重复发送消息 / 指令 │ ├── grimCompat.js ★ Grim 反作弊合规层 + 协议补包(player_input / tick_end) │ ├── pathingService.js 寻路服务(替代 Baritone) │ ├── autoCombat.js 战斗筛选与耐久检查 │ ├── autoEat.js 自动进食 │ ├── autoBoat.js 自动用船(默认关闭) │ └── durabilityGuard.js 耐久保护 ├── controller/ │ └── expansionController.js 扩地任务状态机(按机器人实例化) ├── web/ │ ├── server.js Express + WebSocket + 登录鉴权 │ └── public/ 控制台前端(原生 HTML/CSS/JS,无构建步骤) └── util/ └── logger.js 环形日志缓冲 data/ (已 gitignore,运行时生成) ├── autostore.json 全局配置 ├── bots.json 机器人设置(启用 / 备注 / 自动开始 / 配置覆盖,不含密码) ├── expansion-log.jsonl 开拓日志 ├── chat/<机器人>.jsonl 游戏公屏日志 └── mapcache/// 大地图瓦片缓存 ``` --- ## 许可证 MIT