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 行删除
+114 -15
查看文件
@@ -12,12 +12,17 @@
<span class="logo">AS</span>
<div>
<h1>AutoStore Bot</h1>
<p>Minecraft 自动扩地机器人 · mineflayer · Grim 合规</p>
<p>多机器人自动扩地 · mineflayer · Grim 合规 · 大地图</p>
</div>
</div>
<div class="topbar-right">
<label class="inline-field">
当前机器人
<select id="botSelect" class="input input-sm"></select>
</label>
<span id="connBadge" class="badge badge-offline">未连接</span>
<span id="taskBadge" class="badge badge-idle">空闲</span>
<span id="authBadge" class="badge badge-idle" title="控制台登录状态">未登录</span>
<button id="btnStart" class="btn btn-primary">开始扩地</button>
<button id="btnStop" class="btn btn-danger">停止</button>
<button id="btnReconnect" class="btn">重连</button>
@@ -27,6 +32,17 @@
<main class="layout">
<!-- ==================== 左栏:状态面板 ==================== -->
<section class="column col-left">
<div class="card">
<div class="card-head">
<h2>机器人(<span id="botCount">0</span>)</h2>
<div class="row">
<button id="btnStartAll" class="btn btn-sm">全部开始</button>
<button id="btnStopAll" class="btn btn-sm">全部停止</button>
</div>
</div>
<div id="botList" class="bot-list"></div>
</div>
<div class="card">
<h2>连接</h2>
<dl class="kv">
@@ -37,8 +53,14 @@
<dt>所在区块</dt><dd id="kChunk">-</dd>
<dt>生命 / 饥饿</dt><dd id="kHealth">-</dd>
<dt>状态</dt><dd id="kBotState">-</dd>
<dt>登录验证</dt><dd id="kLogin">-</dd>
<dt>重连次数</dt><dd id="kReconnect">0</dd>
</dl>
<div class="row">
<label class="chk"><input type="checkbox" id="chkEnabled"> 启用该机器人</label>
<label class="chk"><input type="checkbox" id="chkAutoStart"> 启动后自动扩地</label>
<button id="btnSaveBotSetting" class="btn btn-sm">保存</button>
</div>
</div>
<div class="card">
@@ -46,6 +68,7 @@
<dl class="kv">
<dt>状态机</dt><dd id="kTaskState">IDLE</dd>
<dt>描述</dt><dd id="kTaskStatus">未启动</dd>
<dt>开拓邦国</dt><dd id="kTaskStateName">-</dd>
<dt>当前目标</dt><dd id="kTarget">-</dd>
<dt>队列</dt><dd id="kQueue">0</dd>
<dt>已完成</dt><dd id="kCompleted">0</dd>
@@ -90,12 +113,22 @@
</div>
</section>
<!-- ==================== 中栏:地图 ==================== -->
<!-- ==================== 中栏:地图与日志 ==================== -->
<section class="column col-center">
<div class="card card-map">
<div class="card-head">
<h2>领土地图</h2>
<div class="row">
<div class="tabs tabs-inline" id="mapTabs">
<button class="tab active" data-view="big">大地图</button>
<button class="tab" data-view="territory">领土地图</button>
</div>
<div class="row" id="bigMapControls">
<label class="chk"><input type="checkbox" id="chkTerritory" checked> 领土标记</label>
<label class="chk"><input type="checkbox" id="chkBotLabels" checked> 机器人名字</label>
<button id="btnMapFitWorld" class="btn btn-sm">全世界</button>
<button id="btnMapCenterBots" class="btn btn-sm">回到机器人</button>
<button id="btnCrawlMap" class="btn btn-sm">爬取地图</button>
</div>
<div class="row hidden" id="territoryMapControls">
<label class="chk"><input type="checkbox" id="chkFull" checked> 显示全部邦国</label>
<label class="chk"><input type="checkbox" id="chkFlying" checked> 标出飞地</label>
<label class="chk"><input type="checkbox" id="chkGrid" checked> 网格</label>
@@ -105,30 +138,65 @@
</div>
<canvas id="mapCanvas"></canvas>
<div class="map-legend">
<span><i class="sw sw-self"></i>己方领土</span>
<span><i class="sw sw-session"></i>本次占领</span>
<span><i class="sw sw-flying"></i>飞地(疏漏)</span>
<span><i class="sw sw-target"></i>当前目标</span>
<span><i class="sw sw-player"></i>机器人</span>
<span><i class="sw sw-capital"></i>王城</span>
<span id="legendBig">
<i class="sw sw-self"></i>己方领土
<i class="sw sw-session"></i>本次占领
<i class="sw sw-capital"></i>王城
<i class="sw sw-bot"></i>机器人
</span>
<span id="legendTerritory" class="hidden">
<i class="sw sw-self"></i>己方领土
<i class="sw sw-session"></i>本次占领
<i class="sw sw-flying"></i>飞地(疏漏)
<i class="sw sw-target"></i>当前目标
<i class="sw sw-player"></i>机器人
<i class="sw sw-capital"></i>王城
</span>
<span id="mapInfo" class="mono small"></span>
</div>
</div>
<div class="card card-log">
<div class="card-head">
<h2>运行日志</h2>
<div class="row">
<div class="tabs tabs-inline" id="logTabs">
<button class="tab active" data-pane="log">运行日志</button>
<button class="tab" data-pane="chat">公屏聊天</button>
<button class="tab" data-pane="exp">开拓日志</button>
</div>
<div class="row" id="logControls">
<label class="chk"><input type="checkbox" id="chkAutoScroll" checked> 自动滚动</label>
<button id="btnClearLog" class="btn btn-sm">清屏</button>
<button id="btnExportLog" class="btn btn-sm">导出日志</button>
</div>
<div class="row hidden" id="chatControls">
<label class="chk"><input type="checkbox" id="chkChatAutoScroll" checked> 自动滚动</label>
<label class="chk"><input type="checkbox" id="chkChatAllBots"> 全部机器人</label>
<button id="btnClearChat" class="btn btn-sm">清屏</button>
<button id="btnExportChat" class="btn btn-sm">导出公屏日志</button>
</div>
<div class="row hidden" id="expControls">
<label class="chk">机器人
<select id="expFilterBot" class="input input-sm"><option value="">全部</option></select>
</label>
<label class="chk"><input type="checkbox" id="chkExpAutoScroll" checked> 自动滚动</label>
<button id="btnExportExp" class="btn btn-sm">导出 CSV</button>
<button id="btnClearExp" class="btn btn-sm">清屏</button>
</div>
</div>
<div id="logView" class="log-view"></div>
<div id="chatView" class="log-view hidden"></div>
<div id="expView" class="log-view hidden">
<table class="exp-table">
<thead>
<tr><th>时间</th><th>机器人</th><th>邦国</th><th>区块</th><th>方块坐标</th><th>来源</th></tr>
</thead>
<tbody id="expBody"></tbody>
</table>
</div>
</div>
</section>
<!-- ==================== 右栏:配置 ==================== -->
<!-- ==================== 右栏:配置与控制 ==================== -->
<section class="column col-right">
<div class="card card-config">
<div class="card-head">
@@ -138,12 +206,17 @@
<button id="btnReloadConfig" class="btn btn-sm">重新载入</button>
</div>
</div>
<label class="chk config-scope">
<input type="checkbox" id="chkBotOverride">
仅对当前机器人生效(不勾选则写入全局默认配置)
</label>
<div class="tabs" id="configTabs">
<button class="tab active" data-tab="strategy">策略</button>
<button class="tab" data-tab="timing">超时</button>
<button class="tab" data-tab="pathing">寻路</button>
<button class="tab" data-tab="survival">生存</button>
<button class="tab" data-tab="data">数据</button>
<button class="tab" data-tab="bigmap">大地图</button>
<button class="tab" data-tab="grim">Grim</button>
<button class="tab" data-tab="server">服务器</button>
</div>
@@ -151,9 +224,17 @@
</div>
<div class="card">
<h2>手动指令</h2>
<div class="card-head">
<h2>控制台 / 聊天框</h2>
<span class="mono small" id="consoleTarget">-</span>
</div>
<p class="hint" id="consoleHint">登录验证通过后可向该机器人发送指令或公屏消息。</p>
<div class="row">
<input id="cmdInput" class="input" type="text" placeholder="u claimmode(不带斜杠)">
<select id="sendMode" class="input input-sm">
<option value="chat">公屏聊天</option>
<option value="command">游戏指令</option>
</select>
<input id="cmdInput" class="input" type="text" placeholder="输入内容后回车发送">
<button id="btnSendCmd" class="btn btn-sm">发送</button>
</div>
<div class="row">
@@ -162,10 +243,28 @@
<button class="btn btn-sm" data-cmd="spawn">/spawn</button>
<button class="btn btn-sm" data-cmd="list">/list</button>
</div>
<div class="row">
<input id="quickChatInput" class="input" type="text" placeholder="快捷公屏消息(如:大家好)">
<button id="btnQuickChat" class="btn btn-sm">公屏发送</button>
</div>
</div>
</section>
</main>
<!-- 登录遮罩:控制台配置了 WEB_TOKEN / WEB_PASSWORD 时显示 -->
<div id="loginOverlay" class="overlay hidden">
<div class="overlay-box">
<h2>控制台登录</h2>
<p class="hint">验证通过后才允许操作各机器人的控制台、聊天框与扩地任务。</p>
<input id="loginPassword" class="input" type="password" placeholder="控制台密码 / 令牌" autocomplete="current-password">
<div class="row">
<button id="btnLogin" class="btn btn-primary">登录</button>
<button id="btnLogout" class="btn btn-sm">退出登录</button>
</div>
<p id="loginError" class="hint error-text"></p>
</div>
</div>
<div id="toast" class="toast"></div>
<script src="app.js"></script>
</body>