地址与星门注册表
星门之间的互联完全依靠一个 7 位地址。地址由 StargateAddress 表示,所有地址与坐标的对应关系由 StargateRegistry 这个 WorldSavedData 全局维护。本文所有数字(7 位、16 符号、20 格搜索)均取自 StargateAddress.java、StargateRegistry.java 与 TileDialingDevice.java 源码。
基本信息
| 属性 | 值 |
|---|---|
| 类型 | 特殊机制(全局地址系统 / 世界存档数据) |
| 触发条件 | 玩家在 拨号装置 中设定并锁定地址 |
| 地址位数 | 7 位 sigil |
| 每位取值 | 0–15,共 16 个符号 |
| 地址空间 | 16⁷ = 268,435,456 个不同地址 |
| 持久化 | WorldSavedData,存档名 StargateRegistry |
| 作用域 | 仅主世界(dimensionId == 0) |
符号字母表(§2.14 变体穷举)
StargateAddress.getSigilChar(int i) 把 0–15 映射到 16 个字母。这就是拨号界面上 7 个按钮各自的全部选项,逐项如下:
| 数值 | 字符 | 数值 | 字符 |
|---|---|---|---|
| 0 | A |
8 | M |
| 1 | O |
9 | I |
| 2 | T |
10 | E |
| 3 | D |
11 | J |
| 4 | P |
12 | G |
| 5 | F |
13 | K |
| 6 | H |
14 | B |
| 7 | L |
15 | C |
[!NOTE]
default -> 'Z'分支:任何不在 0–15 范围内的数值都会渲染成字母Z。正常路径不会产生该值——拨号按钮的.length(16)循环按钮和随机生成的rng.nextInt(16)都严格限定在 0–15,UITextures.getSigil()也会clamp(i, 0, 15)。Z只是防御性兜底。
地址字符串形式为 7 个字符各跟一个空格(toString() 中每轮都 append(' ')),所以末尾带一个多余空格,例如 A T D P F H L 。WAILA 提示栏就显示这个字符串。
地址的 7 个分量
地址是一个 int[7],构造时用 Arrays.copyOf(sigils, 7) 强制补齐或截断到恰好 7 位。第 i 位对应星门拱门上第 i 个雪佛龙:
| 分量 | 下标 | 含义 | 取值 |
|---|---|---|---|
| sigil[0] | 0 | 第一个雪佛龙锁定的符号 | 0–15 |
| sigil[1] | 1 | 第二个雪佛龙 | 0–15 |
| sigil[2] | 2 | 第三个雪佛龙 | 0–15 |
| sigil[3] | 3 | 第四个雪佛龙 | 0–15 |
| sigil[4] | 4 | 第五个雪佛龙 | 0–15 |
| sigil[5] | 5 | 第六个雪佛龙 | 0–15 |
| sigil[6] | 6 | 第七个雪佛龙 | 0–15 |
7 个分量的地位完全对等——源码里没有任何一位被赋予"星系/行星"之类的语义,也没有任何分段或进位逻辑。地址就是一个扁平的 7 位 16 进制标记,各位之间无算术关系。equals() / hashCode() 直接对整个 int[] 做 Arrays.equals / Arrays.hashCode,因此只有 7 位全部相同才是同一地址。
注册表机制
StargateRegistry 持有一个 HashBiMap<StargateAddress, BlockPos>,严格双向一对一:
register(addr, pos)/unregister(addr)— 每次调用都markDirty()触发存档。lookup(StargateAddress)→ 坐标(正向,由地址找星门)。lookup(BlockPos)→ 地址(反向,由坐标找地址)。
由于是 BiMap,同一个地址不能被两个星门占用,同一个坐标也不能挂两个地址。
设定地址
TileStargateController.setAddress(int[] sigils):
if (hasAddress) return;— 已设定地址则直接返回,一个星门只能设定一次。- 若注册表中
lookup(sga) != null(地址已被占用)→ 静默放弃,不注册、不置hasAddress。 - 否则
register(sga, 本方块坐标),置hasAddress = true与address = sga。
[!WARNING] 占用失败是静默的。 若目标地址已被别的星门使用,
hasAddress仍为false,GUI 会一直停留在"设定地址"界面而不是给出错误提示。唯一的事前检查是 GUI 里的isUnique同步值(lookup(...) == null),实时显示"地址可用 / 已被使用"。随机生成按钮用do { … } while (lookup != null)拒绝采样来规避冲突。
解除注册
TileStargateController.invalidate() 在方块失效(被拆除、区块卸载)时,若 hasAddress 则 unregister(address)。因此拆掉星门会释放它的地址,之后可以被别的星门重新占用。
持久化与加载范围
- 存档名常量
StargateRegistry.DATA_NAME = "StargateRegistry"。 - NBT 结构:一个
Entries标签列表,每项含 int 数组addr加整数x/y/z。 StargateRegistry.RegisterEvent在WorldEvent.Load时仅当!world.isRemote且world.provider.dimensionId == 0(主世界)才加载或新建INSTANCE。
[!IMPORTANT] 注册表是全服共享的单一实例,且只跟随主世界。 任意主世界内的星门都能通过地址找到其他任意星门,与距离、维度、加载状态无关。
StargateRegistry.INSTANCE是静态字段,多个存档共享同一个 Java 静态引用但各自持有独立的mapStorage副本。
限制与边界情况
- 地址只在主世界注册(
dimensionId == 0)。其他维度的星门无法被主世界地址索引到。 - Tile NBT 只保存
hasAddress布尔值(writeToNBT/readFromNBT各一行)。地址本身不存入方块 NBT,而是在updateEntity()里通过StargateRegistry.INSTANCE.lookup(new BlockPos(x, y, z))按坐标反查恢复。- 推论:若注册表数据丢失(例如世界存档损坏)而方块 NBT 完好,则
hasAddress仍为true但address变为null,此时getAddressString()会直接抛 NPE,WAILA 显示地址也会出问题。
- 推论:若注册表数据丢失(例如世界存档损坏)而方块 NBT 完好,则
- 坐标反查依赖
BlockPos的equals/hashCode:注册表用BlockPos作键,若 gtnhlib 的BlockPos相等性实现与实际坐标不一致,跨区块的星门会解析不出地址。 dialed/dialing等连接状态完全不持久化,区块卸载后重置。- 8 万多个地址位组合下,玩家手动撞地址的实际概率极低,但"随机生成"按钮不设重试上限——理论上存在无限循环的可能(实践中因 16⁷ 空间而不会发生)。