占位符替换(StringReplacer)

基本信息

属性 值
源码类 lumien.custommainmenu.lib.StringReplacer(全静态)
静态占位符 replacePlaceholders(7 个)
动态占位符 dynamicPlaceholders + replaceDynamicPlaceholders(2 个)
生效范围 按钮文本、按钮提示框、文本元素

7 个静态占位符

replacePlaceholders(StringReplacer.java:23-30),用 String.replace 逐个链式替换:

占位符 替换为 来源
#mcversion# 硬编码字面量 "1.7.10" ⚠️ 见下方说明
#fmlversion# Loader.instance().getFMLVersionString() FML
#mcpversion# 反射读 Loader.mcpversion 静态块反射
#modsloaded# Loader.getModList().size() 已加载 mod 总数
#modsactive# Loader.getActiveModList().size() 实际激活的 mod 数
#forgeversion# ForgeVersion.getVersion() Forge
#username# Minecraft.getSession().getUsername() 当前登录名

⚠️ #mcversion# 是写死的 "1.7.10"(StringReplacer.java:24),不读 Minecraft.getMinecraft().getMinecraftVersion()。在 1.7.10 整合包里结果恰好正确,但源码层面它并不"动态"。

⚠️ #mcpversion# 依赖反射 Forge 私有静态字段 Loader.mcpversion。GTNH 改版 Forge 若重命名该字段,反射失败 → printStackTrace → mcpversion 保持 null → String.replace(target, null) 抛 NullPointerException,会让所有用到该占位符的文本渲染失败。这是 GTNH 环境下最值得关注的一处脆弱点。

2 个动态占位符

dynamicPlaceholders = { "#date#", "#time#" }(StringReplacer.java:21):

占位符 格式 说明
#date# DateFormat.getDateInstance(2, Locale.getDefault()) 跟随系统语言与地区的本地化日期
#time# SimpleDateFormat("HH:mm") 24 小时制本地时间

两者都每帧重新计算(见下),所以可以用来做实时时钟。

何时替换

渲染层按"先静态、命中动态才继续"的顺序处理(GuiCustomButton.java:113-122、GuiCustomLabel.java:87-95):

String text = StringReplacer.replacePlaceholders(hovered ? hoverText.get() : text.get());
for (String p : StringReplacer.dynamicPlaceholders) {
    if (text.contains(p)) return StringReplacer.replaceDynamicPlaceholders(text);
}
return text;
场景 行为
文本不含 #date# / #time# 只做静态替换,无每帧开销
文本含其一 追加一次动态替换(DateFormat 格式化每帧执行)

提示框走另一条路

按钮 tooltip 不经过上面的逻辑,而是由 LogicUtil.getTooltip 按行调 replacePlaceholders(LogicUtil.java:11-16):

  • 只做静态替换,⚠️ #date# / #time# 在提示框里不会被替换,会原样显示这两个串。
  • 按 \n 拆行成 ArrayList<String> 交给原版悬停框。

已知限制

  • 纯 String.replace,无转义机制:想显示字面量 #date# 做不到。
  • 静态占位符在 文本来源 解析之后才替换,所以 web: 拉回来的内容同样能享受替换。
  • 飘字(SplashText)不经过 StringReplacer——drawScreen 直接 drawCenteredString(GuiCustom.java:269),飘字里写占位符不会生效。

相关条目