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)。