feat: 支持多机器人挂机、开拓日志、公屏完整日志与大地图

多机器人(src/bot/botPool.js、src/config/botsConfig.js)
- .env 用 MC_BOTS 一次配置多个账号(JSON 数组或 用户名:密码 逗号分隔),未配置时回退单机器人
- 每个机器人独立连接 / 登录 / 扩地状态机,共享一份领土数据服务与运行日志
- 密码只从 .env 读取;data/bots.json 只存启用状态、备注名、自动开始与各自的配置覆盖
- 每个机器人可覆盖全局配置(策略 / 目标邦国 / 目标数量等),未覆盖字段回退全局默认

开拓日志(src/core/expansionLog.js)
- 逐条记录开拓玩家、邦国、区块、方块坐标、时间与判定来源,追加写入 data/expansion-log.jsonl
- 支持按机器人 / 邦国 / 时间筛选、统计、导出带 BOM 的 CSV,超过 16MB 自动轮转

游戏公屏完整日志与聊天框(src/core/chatLog.js)
- 每个机器人的全部公屏 / 系统 / 动作栏消息按机器人分文件留档,自身发出的聊天与指令同样留档
- 控制台新增公屏聊天页签与消息发送框

登录验证门禁
- 游戏侧:未完成登录验证时拒绝发送指令 / 聊天,isReady() 也不放行扩地任务;
  新增 server.loginConfirmTimeoutMs 兜底,避免服务器不回提示导致永远卡在未登录
- 控制台侧:新增 WEB_PASSWORD 与 /api/login、/api/session,页面登录框验证通过才允许操作

大地图(src/core/bigMapService.js)
- 爬取服务器地图站点(squaremap)的瓦片图片与领土标记,一张图上看全部机器人坐标
- 坐标系对源站实测标定并用 27 个王城交叉验证;瓦片磁盘缓存,层级 0 启动预爬取、更高层级按需穿透
- 领土按 markers.json 原始多边形绘制(预编译 Path2D,even-odd 保留孔洞),叠加王城与机器人标签

配套改造
- TerritoryModel 新增 flyingChunksOf / ownChunksOf,邦国归属改为按名称显式传入,
  不再依赖全局 selfStateName(多机器人会互相覆盖)
- Web 接口以 /api/bots/:id/... 为主,原单机器人接口保留为默认机器人的等价别名
- 新增 scripts/check-web-ids.js 校验前端元素引用一致性

测试从 154 增至 242,全部通过。
这个提交包含在:
lyizai
2026-09-27 21:22:57 +08:00
父节点 223cf719b6
当前提交 39624da2d5
共修改 30 个文件,包含 5480 行新增和 362 行删除
+33
查看文件
@@ -173,6 +173,24 @@ function defaultConfig() {
blockActionsWhileRiding: true
},
// ==================== 大地图(新增) ====================
bigMap: {
/** 是否启用大地图(爬取服务器地图站点的瓦片图片与领土标记) */
enabled: true,
/** 地图站点上的世界目录名;留空则回退环境变量 DYNMAP_WORLD */
world: '',
/** 启动时预爬取的层级(0 = 全世界一张大图,本站为 10×10=100 张瓦片) */
zoom: 0,
/** 允许浏览的最大层级(本站 squaremap 的 zoom.max 为 3) */
maxZoom: 3,
/** 以原点为中心的覆盖半径(方块),默认与服务器世界边界 ±20480 一致 */
rangeBlocks: 20480,
/** 启动时是否自动预爬取 */
crawlOnStart: true,
/** 瓦片下载并发数(对源站友好,默认 4) */
concurrency: 4
},
// ==================== 服务器交互(新增) ====================
server: {
/** 启用开垦模式的指令(不带斜杠),留空表示服务器自动开启 */
@@ -181,6 +199,12 @@ function defaultConfig() {
loginCommand: 'login {password}',
/** 注册指令模板,`{password}` 会被替换;留空则不发送 */
registerCommand: '',
/**
* 登录结果确认超时(毫秒)。
* 部分离线服登录成功后不回明确提示,超时后按「已登录」放行,
* 否则机器人会永远卡在未登录状态而无法接受控制台操作;设为 0 表示禁用兜底。
*/
loginConfirmTimeoutMs: 10000,
/** 聊天指令最小间隔(毫秒),防刷屏 */
commandCooldownMs: 1200,
/** 断线后是否自动重连 */
@@ -277,8 +301,17 @@ function validateConfig(config) {
config.grim.maxRotationPerTickDeg = clampNumber(config.grim.maxRotationPerTickDeg, 1, 180, 8.6);
config.grim.attackReach = clampNumber(config.grim.attackReach, 1, 6, 3.0);
config.bigMap.zoom = clampInt(config.bigMap.zoom, 0, 8, 0);
config.bigMap.maxZoom = clampInt(config.bigMap.maxZoom, 0, 8, 3);
// 预爬取层级不可能超过可浏览的最大层级,否则会爬到源站不存在的瓦片
if (config.bigMap.zoom > config.bigMap.maxZoom) config.bigMap.zoom = config.bigMap.maxZoom;
config.bigMap.rangeBlocks = clampInt(config.bigMap.rangeBlocks, 256, 200000, 20480);
config.bigMap.concurrency = clampInt(config.bigMap.concurrency, 1, 16, 4);
if (typeof config.bigMap.world !== 'string') config.bigMap.world = '';
config.server.commandCooldownMs = clampInt(config.server.commandCooldownMs, 0, 60000, 1200);
config.server.reconnectDelayMs = clampInt(config.server.reconnectDelayMs, 1000, 600000, 10000);
config.server.loginConfirmTimeoutMs = clampInt(config.server.loginConfirmTimeoutMs, 0, 300000, 10000);
if (config.waterMode !== WATER_MODE.SWIM && config.waterMode !== WATER_MODE.BOAT) {
config.waterMode = WATER_MODE.AVOID;