feat: 大地图跟随机器人并支持可调图幅,新增定时消息,控制台界面重做并适配移动端
大地图跟随机器人 + 可调图幅 - 爬取与显示区域改为以机器人当前位置为中心(bigMap.followBot 默认开启), 不再傻傻地爬世界原点周边 - 界面新增「图幅」下拉(±512/1024/2048/4096/8192/全世界)与「爬取周边」按钮, 只爬当前图幅内的瓦片:层级 0 每张覆盖 4096 格,±4096 只需 9 张,全世界要 100 张 - POST /api/bigmap/crawl 支持 centerX/centerZ/rangeBlocks;GET /api/bigmap 支持 rangeBlocks/bot 查询参数;启动预爬取会先等机器人进入世界再以其位置为中心 领土地图以机器人所在邦国为中心 - 之前初始视图用的是控制器的 playerChunk,而任务启动前它是 (0,0), 导致地图永远停在世界原点附近。现在按「机器人所在区块 → 包含它的邦国 → 该邦国王城/外接矩形」定位 定时重复发送消息(src/bot/scheduledMessenger.js) - 每个机器人可配多条定时任务,按固定间隔重复发送同一内容,如每 300 秒 /u restore confirm - 以 / 开头按游戏指令发送,否则按公屏消息发送 - 只在已连接且通过登录验证时发送;未就绪的周期顺延且不补发,避免重连后一次性连发 - 与指令节流队列共用,间隔下限 5 秒、单机器人最多 20 条 - 控制台新增「定时消息」卡片(新增/删除/启停 + 剩余秒数倒计时) 控制台界面重做 + 移动端适配 - 重新设计的深色主题:统一圆角、阴影、焦点态与悬停态,卡片式分层 - 响应式三档:≥1200px 三栏;≤1200px 两栏;≤820px 单栏 + 底部导航 - 移动端:触控目标 ≥36px、地图/日志高度按视口自适应、避让底部安全区、 横屏压矮地图、尊重「减少动效」偏好、Toast 全宽 - 「添加玩家」折叠进 details;顶栏窄屏自动换行且按钮等分 修复 - POST /api/bigmap/crawl 响应把中心与图幅字段传错(实际爬取正确,返回值显示成原点) - 定时任务保存后接口返回原始列表而非归一化结果,前端拿不到剩余秒数 测试 308 -> 325,全部通过。
这个提交包含在:
+47
-17
@@ -86,22 +86,40 @@ class BigMapService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算覆盖 `[-rangeBlocks, rangeBlocks)` 区间所需的瓦片下标范围(闭区间)。
|
||||
* 计算覆盖以 `center` 为中心、半径 `rangeBlocks` 的方形区域所需的瓦片下标范围(闭区间)。
|
||||
*
|
||||
* 默认中心是原点(全世界),但实际使用时通常只关心**机器人所在位置周边**的图幅 ——
|
||||
* 全世界层级 0 是 100 张瓦片,而 1024 格半径只需 1 张、4096 格半径只需 4 张,
|
||||
* 爬取和传输的开销差一个数量级。
|
||||
*
|
||||
* @param {number} zoom 层级
|
||||
* @param {number} [rangeBlocks] 覆盖半径(方块),缺省取配置
|
||||
* @returns {{minX: number, maxX: number, minZ: number, maxZ: number, count: number}}
|
||||
* @param {number} [centerBlockX] 中心方块 X,缺省 0
|
||||
* @param {number} [centerBlockZ] 中心方块 Z,缺省 0
|
||||
* @returns {{minX: number, maxX: number, minZ: number, maxZ: number, count: number,
|
||||
* centerBlockX: number, centerBlockZ: number, rangeBlocks: number}}
|
||||
*/
|
||||
tileRange(zoom, rangeBlocks) {
|
||||
tileRange(zoom, rangeBlocks, centerBlockX, centerBlockZ) {
|
||||
const radius = Number.isFinite(rangeBlocks)
|
||||
? Math.abs(rangeBlocks)
|
||||
: (this.config && this.config.bigMap ? this.config.bigMap.rangeBlocks : DEFAULT_RANGE_BLOCKS);
|
||||
const centerX = Number.isFinite(centerBlockX) ? centerBlockX : 0;
|
||||
const centerZ = Number.isFinite(centerBlockZ) ? centerBlockZ : 0;
|
||||
const perTile = this.blocksPerTile(zoom);
|
||||
const minX = Math.floor(-radius / perTile);
|
||||
const maxX = Math.ceil(radius / perTile) - 1;
|
||||
const minZ = minX;
|
||||
const maxZ = maxX;
|
||||
return { minX, maxX, minZ, maxZ, count: (maxX - minX + 1) * (maxZ - minZ + 1) };
|
||||
const minX = Math.floor((centerX - radius) / perTile);
|
||||
const maxX = Math.ceil((centerX + radius) / perTile) - 1;
|
||||
const minZ = Math.floor((centerZ - radius) / perTile);
|
||||
const maxZ = Math.ceil((centerZ + radius) / perTile) - 1;
|
||||
return {
|
||||
minX,
|
||||
maxX,
|
||||
minZ,
|
||||
maxZ,
|
||||
count: (maxX - minX + 1) * (maxZ - minZ + 1),
|
||||
centerBlockX: centerX,
|
||||
centerBlockZ: centerZ,
|
||||
rangeBlocks: radius
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,19 +228,22 @@ class BigMapService {
|
||||
// ==================== 批量爬取 ====================
|
||||
|
||||
/**
|
||||
* 批量爬取某一层级覆盖范围内的全部瓦片(已缓存则跳过)。
|
||||
* 批量爬取某一层级、某一区域内的全部瓦片(已缓存则跳过)。
|
||||
*
|
||||
* 区域默认以原点为中心;实际使用时通常传机器人所在坐标,只爬它周边那几平方公里,
|
||||
* 这样图幅可以自由调整而不用把整个世界都拉下来。
|
||||
*
|
||||
* 重复调用时若已有爬取在途则直接返回当前进度,不会并发起多轮。
|
||||
*
|
||||
* @param {number} [zoom] 层级,缺省取配置里的 bigMap.zoom
|
||||
* @param {{force?: boolean}} [options] force=true 时忽略磁盘缓存重新下载
|
||||
* @param {{force?: boolean, centerBlockX?: number, centerBlockZ?: number, rangeBlocks?: number}} [options]
|
||||
* @returns {Promise<object>} 爬取结果统计
|
||||
*/
|
||||
async crawlAll(zoom, options) {
|
||||
if (this.crawl.running) return this.snapshot().crawl;
|
||||
const z = clampZoom(Number.isFinite(zoom) ? zoom : (this.config.bigMap.zoom || 0), this.maxZoom);
|
||||
const opts = options || {};
|
||||
const range = this.tileRange(z);
|
||||
const z = clampZoom(Number.isFinite(zoom) ? zoom : (this.config.bigMap.zoom || 0), this.maxZoom);
|
||||
const range = this.tileRange(z, opts.rangeBlocks, opts.centerBlockX, opts.centerBlockZ);
|
||||
this._crawlAbort = false;
|
||||
this.crawl = {
|
||||
running: true,
|
||||
@@ -232,10 +253,14 @@ class BigMapService {
|
||||
failed: 0,
|
||||
startedAt: Date.now(),
|
||||
finishedAt: 0,
|
||||
lastError: ''
|
||||
lastError: '',
|
||||
centerBlockX: range.centerBlockX,
|
||||
centerBlockZ: range.centerBlockZ,
|
||||
rangeBlocks: range.rangeBlocks
|
||||
};
|
||||
if (this.logger) {
|
||||
this.logger.log(`开始爬取大地图瓦片:层级 ${z},范围 ${range.minX}..${range.maxX} × ${range.minZ}..${range.maxZ},共 ${range.count} 张`);
|
||||
this.logger.log(`开始爬取大地图瓦片:层级 ${z},中心 ${range.centerBlockX},${range.centerBlockZ},`
|
||||
+ `半径 ${range.rangeBlocks} 格,瓦片 ${range.minX}..${range.maxX} × ${range.minZ}..${range.maxZ},共 ${range.count} 张`);
|
||||
}
|
||||
|
||||
const tasks = [];
|
||||
@@ -285,11 +310,15 @@ class BigMapService {
|
||||
// ==================== 状态 ====================
|
||||
|
||||
/**
|
||||
* @param {{centerBlockX?: number, centerBlockZ?: number, rangeBlocks?: number}} [options]
|
||||
* @returns {object} 大地图状态快照(供控制台与前端渲染使用)
|
||||
*/
|
||||
snapshot() {
|
||||
snapshot(options) {
|
||||
const opts = options || {};
|
||||
const zoom = this.config && this.config.bigMap ? this.config.bigMap.zoom : 0;
|
||||
const range = this.tileRange(zoom);
|
||||
const centerX = Number.isFinite(opts.centerBlockX) ? opts.centerBlockX : 0;
|
||||
const centerZ = Number.isFinite(opts.centerBlockZ) ? opts.centerBlockZ : 0;
|
||||
const range = this.tileRange(zoom, opts.rangeBlocks, centerX, centerZ);
|
||||
return {
|
||||
enabled: !!(this.config && this.config.bigMap && this.config.bigMap.enabled),
|
||||
baseUrl: this.baseUrl,
|
||||
@@ -298,7 +327,8 @@ class BigMapService {
|
||||
maxZoom: this.maxZoom,
|
||||
zoom,
|
||||
blocksPerTile: this.blocksPerTile(zoom),
|
||||
rangeBlocks: this.config && this.config.bigMap ? this.config.bigMap.rangeBlocks : DEFAULT_RANGE_BLOCKS,
|
||||
rangeBlocks: range.rangeBlocks,
|
||||
followBot: !!(this.config && this.config.bigMap && this.config.bigMap.followBot),
|
||||
tiles: range,
|
||||
cached: this.countCached(zoom),
|
||||
source: {
|
||||
|
||||
在新工单中引用
屏蔽一个用户