地址与星门注册表

星门之间的互联完全依靠一个 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):

  1. if (hasAddress) return; — 已设定地址则直接返回,一个星门只能设定一次。
  2. 若注册表中 lookup(sga) != null(地址已被占用)→ 静默放弃,不注册、不置 hasAddress。
  3. 否则 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 显示地址也会出问题。
  • 坐标反查依赖 BlockPos 的 equals/hashCode:注册表用 BlockPos 作键,若 gtnhlib 的 BlockPos 相等性实现与实际坐标不一致,跨区块的星门会解析不出地址。
  • dialed / dialing 等连接状态完全不持久化,区块卸载后重置。
  • 8 万多个地址位组合下,玩家手动撞地址的实际概率极低,但"随机生成"按钮不设重试上限——理论上存在无限循环的可能(实践中因 16⁷ 空间而不会发生)。

相关条目

  • 星门 - 使用地址的多方块结构本体
  • 拨号装置 - 设定地址与发起拨号的界面所在方块
  • 拨号序列 - 拨出后 7 个雪佛龙依次锁定的动画状态机