Navigator 地图图层整合

装了 Navigator 时,已记录的节点会以图层形式出现在 JourneyMap / Xaero World Map 上。

加载检测与注册

TCNodeTracker.preInit(TCNodeTracker.java:59):

isNavigatorLoaded = Loader.isModLoaded("navigator");

init(:72-74)在检测到后调用 NavigatorIntegration.init(), 后者只有一行(integration/navigator/NavigatorIntegration.java:9):

NavigatorApi.registerLayerManager(ThaumcraftNodeLayerManager.instance);

源码注释说明了为何类要单独拆出去:

// Forge initializes this class if it's put in the @Mod class even if it's behind a Loader.isModLoaded flag

即把 @Mod 类里对 Navigator 的符号引用隔离出去,避免 Forge 预初始化时因 Navigator 缺失而崩溃。

依赖声明在 lib/Constants.java:8:

required-after:gtnhlib@[0.9.57,);required-after:Thaumcraft;after:navigator

navigator 是 after(软依赖)。没有 Navigator 时本 mod 依然可用, 只是失去地图图层与切换节点图层热键。

ThaumcraftNodeLayerManager

integration/navigator/ThaumcraftNodeLayerManager.java,extends InteractableLayerManager, 静态单例 instance,构造时把 ThaumcraftNodeButtonManager.instance 交给父类。

图层渲染器与地图标记(:28-34)

addLayerRenderer 返回一个 UniversalInteractableRenderer,配两个工厂:

工厂 产物 用途
.withRenderStep(...) ThaumcraftNodeRenderStep 地图上的可交互图标
.withMapMarker(...) ThaumcraftNodeMapMarker.create(...) 静态地图标记

Waypoint 管理器(:36-44)

addWaypointManager 按地图 mod 分派:

SupportedMods 产物
JourneyMap JMThaumcraftNodeWaypointManager
XaeroWorldMap XaeroThaumcraftNodeWaypointManager
其它 null

两个子类只做两件事(journeymap/JMThaumcraftNodeWaypointManager.java、xaero/XaeroThaumcraftNodeWaypointManager.java):

重写 行为
clearActiveWaypoint() 调父类后 TCNodeTracker.yMarker = -1
updateActiveWaypoint(waypoint) 调父类后把 xMarker / yMarker / zMarker 设为 waypoint 坐标

Xaero 版本额外重写 getSymbol(Waypoint) 固定返回 "@"(xaero/…:29-31),JourneyMap 版本没有。

⚠️ 这个同步是 Navigator 双向的:在 Navigator 地图上设路点,也会让屏幕指引箭头跟着转。

可见性筛选(:46-61)

for (NodeList node : TCNodeTracker.nodelist) {
    if (node.dim == dimension && node.x >= minBlockX && node.x <= maxBlockX
            && node.z >= minBlockZ && node.z <= maxBlockZ) {
        thaumcraftNodeLocations.add(new ThaumcraftNodeLocation(node));
    }
}

线性扫描整个 nodelist,不做任何空间索引,节点多时每次视口变化都是 O(n)。 且只按 X/Z 方框和维度过滤,不按 Y 过滤 —— 同一 X/Z 垂直方向上的所有节点都会出现在地图上。

删除(:68-84)

private void deleteNodeInternal(ThaumcraftNodeLocation loc) {
    TCNodeTracker.nodelist.removeIf(loc::belongsToNode);
    if (activeWaypoint != null) loc.onWaypointUpdated(activeWaypoint);
    if (loc.isActiveAsWaypoint()) clearActiveWaypoint();
    invalidateLocation(loc);
    JsonUtils.writeJson();
}

belongsToNode 比较 (x, y, z, dim) 四项(ThaumcraftNodeLocation.java:100-102)。 删完会通知 waypoint 状态、清活动路点、让图层缓存失效,最后写盘。

ThaumcraftNodeLocation

integration/navigator/ThaumcraftNodeLocation.java,implements IWaypointAndLocationProvider。

稳定 ID:toLong()(:65-70)

return (long) (node.x & 0x3FFFFFF) << 38 | (long) (node.z & 0x3FFFFFF) << 12 | (node.y & 0xFFF);
分量 位宽 位偏移
X 26(0x3FFFFFF) 38
Z 26(0x3FFFFFF) 12
Y 12(0xFFF) 0

源码注释解释了为何不带维度:

Minecraft coordinates fit in 26 bits for X/Z and 12 bits for Y. Navigator caches each dimension separately, so packing only X/Y/Z gives every node in that dimension a stable, unique 64-bit identity.

即 Navigator 按维度分别缓存,所以 64 位里不必再塞维度。

坐标与颜色(:46-62、:73-81)

getBlockX/Y/Z() 全部返回 + 0.5(方块中心)。getDimensionId() 返回 node.dim。

toWaypoint() 的颜色取 getStrongestAspect().getColor(),即节点里数值最大的那个侧重点的颜色:

public Aspect getStrongestAspect() {
    return aspects.getAspectsSortedAmount()[0];
}

(:104-106,用的是 Thaumcraft AspectList 的排序数组首元素。) refresh()(:34-44)把存档里的 aspect map 重建回 AspectList, 并拼出 tooltip 用的 description:节点类型 + 修饰符(mod 为 BLANK 时省略后半段)。

waypoint 状态同步(:83-98)

isActiveAsWaypoint 由 onWaypointUpdated(waypoint) 维护 —— 逐项比较 waypoint 的 dimensionId / blockX / blockY / blockZ 与节点是否全等,全等才为 true; onWaypointCleared() 置回 false。

tooltip 文案

4 个静态文案,在类加载时就用 I18n.format 求值(:18-21):

字段 语言键 en_US
title tcnodetracker.tracked + tile.blockAiry.0.name Tracked: Airy
description nodetype.<type>.name(+ nodemod.<mod>.name) Normal, Bright
activeWaypointHint tcnodetracker.iswaypoint Active as Waypoint
deleteHint tcnodetracker.node.deletehint Mark depleted with %s

⚠️ 因为是 static final 且在静态初始化里调 I18n.format,语言设置在游戏运行中改变时不会刷新 (要重启客户端)。deleteHint 的 %s 由 Keyboard.getKeyName(NavigatorApi.ACTION_KEY.getKeyCode()) 填充,即 Navigator 自己的操作键名。

ThaumcraftNodeRenderStep

integration/navigator/ThaumcraftNodeRenderStep.java, extends UniversalInteractableStep<ThaumcraftNodeLocation>,final。

常量 值 源码
ICON_SIZE 44 :28
ASPECT_SIZE 32 :29
最小缩放 0.5(setMinScale) :33

绘制(:36-55)

preRender 按地图类型决定图标大小:

setSize(isXaero ? ICON_SIZE : ICON_SIZE * drawScale);

Xaero 用固定 44 像素,JourneyMap 用 44 * drawScale。 随后 setOffset(-width / 2) 让图标以中心对齐而非左上角。

draw 分支:

状态 贴图 颜色 alpha
isActiveAsWaypoint() 为真 textures/gui/node_marked.png 常量 0xFFFFFF 204
否则 textures/gui/node_unmarked.png location.getStrongestAspect().getColor() 204

之后用 Utils.drawAspect 在图标正中画最强侧重点的图标(大小 ASPECT_SIZE,数量传 0)。 Utils.drawAspect(lib/Utils.java:19-38)把像素尺寸换算成 pixelSize / 16 的缩放, 再调 Thaumcraft 自己的 UtilsFX.drawTag。

命中判定与操作(:57-71)

double radius = width / 2;
return deltaX * deltaX + deltaY * deltaY <= radius * radius;

以图标中心为圆心、半径 width/2 的圆形命中区(不是矩形)。 onActionKeyPressed() 直接 deleteNode(location) —— 按 Navigator 的操作键即删除该节点。

tooltip(:73-210)

手绘的深色渐变框(背景 0xF0100010,上下边 0x505000FF / 0x5028007F), 布局:activeWaypointHint(仅已标记时)+ title + description,底部一排侧重点图标。

侧重点图标每行 5 个:

int aspectRows = (location.getAspects().size() + 4) / 5;
int aspectColumns = Math.min(location.getAspects().size(), 5);
int tooltipHeight = (waypoint ? 44 : 32) + aspectRows * 16;

tooltip 有双向文字(RTL)支持:fontRenderer.getBidiFlag() 为真时改用 drawRightAligned (按 1.1 倍宽度右对齐)。

ThaumcraftNodeMapMarker

integration/navigator/ThaumcraftNodeMapMarker.java,final + 私有构造,纯静态工具。

create(location)(:37-47)返回缓存的合成图标:

String key = aspect.getTag() + ':' + location.isActiveAsWaypoint();

缓存键只有侧重点 tag + 是否已标记两项,所以图标种类上限 = 侧重点数量 × 2, 与节点总数无关。缓存是 static final Map<String, BufferedImage>,不主动清理。

compose(aspect, marked)(:49-67)用 BufferedImage + Graphics2D 把两张 16 像素贴图 缩放合成到 44×44 的 ARGB 图:边框图打底,侧重点图以 offset = (44-32)/2 = 6 居中。 插值提示为 VALUE_INTERPOLATION_BILINEAR。

tint(image, color)(:69-83)逐像素乘算染色。只有未标记的边框图会被染成侧重点颜色, 侧重点图本身始终染色(:53-54)。

⚠️ load 用 Minecraft.getMinecraft().getResourceManager().getResource(location).getInputStream() (:86),没有判空。若贴图缺失,这里抛 NullPointerException 而非走下面的 catch (IOException e)。

setDisplayZoomScale(1, 2, 3, 5)(:46)声明 4 档缩放档位。

ThaumcraftNodeButtonManager

integration/navigator/ThaumcraftNodeButtonManager.java,静态单例。

重写 返回
getIcon(mod, theme) tcnodetracker:textures/gui/node_icon.png(忽略 mod 与 theme 参数)
getButtonText() 语言键 tcnodetracker.button.nodes → en_US Show TC Nodes

由节点列表热键顺带调用 toggle() (events/KeyInputEvent.java:22-24)。

相关条目