Navigator 集成

在 GTNewHorizons Navigator 地图上叠加两个 Opis 图层。

基本信息

属性 值
依赖模组 Navigator(modid navigator)
依赖类型 可选软依赖,运行时检测
最低版本要求 1.1.7
声明方式 devOnlyNonPublishable('com.github.GTNewHorizons:Navigator:1.1.8:dev')(dependencies.gradle:4)
图层数量 2
图层注册 API NavigatorApi.registerLayerManager(...)
图标资源 opis:textures/icon/chunk_timing.png、chunk_loaded.png
相关配置键 3 个(overlay.refresh_ms、overlay.alpha.timing、overlay.alpha.loaded)

功能

版本门禁

ProxyClient.isNavigatorSupported()(ProxyClient.java:209-222):

  1. Loader.instance().getIndexedModList().get("navigator"),为 null 直接返回 false
  2. VersionParser.parseRange("[1.1.7,)").containsVersion(navigator.getProcessedVersion()) 通过则返回 true
  3. 否则打一条 WARN:Navigator {} is too old for the Opis map overlays, which need 1.1.7 or newer. Overlays disabled. 并返回 false

代码注释说明了为什么需要版本检查(ProxyClient.java:209):The layers use APIs added in Navigator 1.1.7, and nothing pins the version for an optional dependency.

Navigator 的全部 import 都在分支内,ProxyClient.java:203 的注释为 Keeps every Navigator class behind this branch so Opis still loads without it. —— 没有 Navigator 时模组照常加载。

延迟注册

ProxyClient.init() 只调用 OpisLayers.init()(ProxyClient.java:203-207),它做三件事(OpisLayers.java:15-19):

  • ChunkTimeLayerManager.init() / LoadedChunkLayerManager.init() —— 注册消息 handler,开始静默接收数据但不出按钮
  • 把 OpisLayers 自身注册到 FML 事件总线

真正的图层注册推迟到 OpisLayers.show()(OpisLayers.java:36-43),由收到 Message.CLIENT_SHOW_SWING 触发(ProxyClient.java:240-246)。注释:Deferred until first use of Opis so the buttons do not clutter every map. show() 内有 shown 布尔量保证只注册一次。

注释还说明了线程约束:Must run on the client thread, as Navigator's layer list is iterated while rendering.,所以调用方 ProxyClient 用 OpisClientTickHandler.INSTANCE.scheduleOnClientThread(OpisLayers::show) 包了一层。

图层 1:区块更新耗时热力图

ChunkTimeLayerManager,按钮文字 Opis chunk update time(ChunkTimeButtonManager.java:20),图标 chunk_timing.png。

  • 消费 Message.LIST_TIMING_CHUNK_DIM
  • 轮询:onUpdatePre 里检查距上次请求是否超过 modOpis.overlayRefreshInterval,未到就返回;到点则发 PacketReqData(LIST_TIMING_CHUNK_DIM, new SerialInt(player.dimension))
  • 归一化:generateVisibleLocations 按维度取该维度内最大的 getDataSum() 作为分母(ChunkTimeLayerManager.java:91-97),注释解释为 the server's top 100 is global, so a hotter dimension would wash this one out —— 即服务端返回的是全局 Top 100,不按维度排
  • 透明度:Navigator 原生渲染用 modOpis.overlayAlphaTiming(ChunkTimeRenderStep.java:35),JM6 覆盖用 overlayAlphaTiming / 255f(ChunkTimeLayerManager.java:68)
  • 颜色:ChunkTimeLocation.getColor() 用 red = (int) Math.ceil(heat * 255.0),返回 (red << 16) | (255 - red),即 heat=0 纯蓝 0x0000FF → heat=1 纯红 0xFF0000,绿通道线性衰减
  • 视口裁剪:按 chunk.x + 15 < minBlockX || chunk.x > maxBlockX 等条件剔除
  • 交互:双击才响应(click.isDoubleClick()),跳转 SwingUI.showTab(SelectedTab.TIMINGCHUNKS) 并选中对应行;单击不消费事件

图层 2:已加载区块

LoadedChunkLayerManager,按钮文字 Opis loaded chunks(LoadedChunkButtonManager.java:20),图标 chunk_loaded.png。

  • 消费 Message.LIST_CHUNK_LOADED 与 Message.LIST_CHUNK_LOADED_CLEAR
  • 服务端分批下发一组区块,以 CLEAR 消息结尾,客户端收到后才显示(ServerMessageHandler.java:124 注释:Sent last: the client treats it as "batch complete" and only then shows it.)
  • 透明度:Navigator 原生渲染用 modOpis.overlayAlphaLoaded(LoadedChunkRenderStep.java:31),JM6 覆盖用 overlayAlphaLoaded / 255f(LoadedChunkLayerManager.java:67)
  • 颜色:LoadedChunkLocation.getColor() 返回 isForced() ? 0x0000FF : 0x00FF00 —— 强制加载区块为蓝色,普通加载区块为绿色
  • isForced() 判据是 chunk.metadata != 0
  • 交互:只有强制加载区块才响应双击(if (!((LoadedChunkLocation) ...).isForced()) return false;),跳转 SwingUI.showTab(SelectedTab.FORCELOADS);普通区块的点击留给下层
  • onLayerToggled(true) 时把 lastRequest 归零,使重新启用后立即重绘(注释:Snapshot is kept so re-enabling redraws immediately.)

断线清理

OpisLayers 订阅 FMLNetworkEvent.ClientDisconnectionFromServerEvent(OpisLayers.java:22-30),在客户端线程上 clearState() 两个图层。注释说明原因:缓存属于单个服务器,保留会把上一个服的区块画到下一个会话里;事件在 netty 线程触发,必须切回客户端线程。

数值

数值名 值
Navigator 最低版本 1.1.7
轮询间隔默认值 1000 ms(overlay.refresh_ms)
轮询间隔下界 250 ms(overlay.refresh_ms 的 min 参数)
服务端对轮询的节流 200 ms(ServerMessageHandler.MIN_REQUEST_INTERVAL_MS)
服务端每次返回的区块数 100(getTopChunks(100, dim))
耗时图层配色 蓝 #0000FF → 红 #FF0000
加载区块配色 强制 #0000FF,普通 #00FF00
区块边长 16(视口裁剪用 chunk.x + 15)
点击阈值 双击

交互

触发 行为
安装 Navigator ≥ 1.1.7 覆盖层可用但地图上无按钮
玩家执行 /opis 后 地图上出现两个 Opis 图层按钮
点击按钮 每 overlay.refresh_ms 毫秒向服务端轮询一次
调整 overlay.refresh_ms 后重启 轮询频率改变
双击热力图区块 打开面板 Chunks 页并选中该行
双击绿色(普通)区块 无响应
双击蓝色(强制)区块 打开面板 Forced Chunks 页
断开服务器 覆盖层缓存清空

相关条目

  • journeymap - 同一图层在 JourneyMap 6 上的原生覆盖实现
  • config - overlay.* 配置键定义
  • swing-ui - 覆盖层跳转到的面板
  • network-protocol - 轮询使用的消息与 200 ms 服务端节流
  • /opis - 触发 OpisLayers.show() 的唯一途径