类型系统与扩展

基本信息

属性 值
脚本可见类(@ZenClass) 89(api/ 68 + mods/ 20 + runtime/ 1)
类型扩展(@ZenExpansion) 16 处声明,落到 13 个不同类型名
实例方法(@ZenMethod) 205(api/ 140 + mods/ 54 + 其余 11)
属性读取(@ZenGetter) 220
属性写入(@ZenSetter) 25
成员读取 / 写入 @ZenMemberGetter 2 / @ZenMemberSetter 1
类型转换(@ZenCaster) 27(全部无参)
运算符重载(@ZenOperator) 32
括号处理器(@BracketHandler) 4
门控注解(@ModOnly) 23
统计口径 grep -rE '^\s*@Xxx\b' src/main/java --include=*.java | wc -l,行首锚定以排除 javadoc 里的文字提及

注解本身来自外部依赖 com.github.GTNewHorizons:ZenScript(dependencies.gradle:2),本仓 src/main/java 下没有 stanhebben 包;仅 buildSrc/src/main/java/stanhebben/zenscript/annotations/ 存有一份注解源码副本,供构建期字节码扫描与文档生成使用。

三种进入命名空间的路径

MineTweakerAPI.registerClass(Class)(MineTweakerAPI.java:304-326)按注解分派:

注解 动作 源码行
@ZenExpansion GlobalRegistry.registerExpansion —— 合并进已有 TypeExpansion MineTweakerAPI.java:309-311
@ZenClass GlobalRegistry.registerNativeClass —— root.put(name, ...) MineTweakerAPI.java:312-314
@BracketHandler newInstance() 后 registerBracketHandler MineTweakerAPI.java:315-324

关键差异:

  • 扩展是累加的。GlobalRegistry.registerExpansion 只在键不存在时新建 TypeExpansion,之后一律 expansions.get(name).expand(cls, types) 往同一个对象上继续加成员(GlobalRegistry.java:62-77)。多个类声明同一扩展名是设计如此,不是冲突。
  • 类是覆盖的。registerNativeClass 直接 root.put(type.getName(), new SymbolType(type), errors)(GlobalRegistry.java:94),不检查键是否已存在;重复名静默后写覆盖先写。而 registerGlobal 反倒会抛 IllegalArgumentException(GlobalRegistry.java:55-57)—— 两种注册路径的重复名行为不一致。

扩展:13 个类型名由 16 处声明合成

扩展类型名 声明处 门控
any[] expand/ExpandAnyArray.java 无
any[any] expand/ExpandAnyDict.java 无
bool expand/ExpandBool.java 无
byte expand/ExpandByte.java 无
short expand/ExpandShort.java 无
int expand/ExpandInt.java 无
long expand/ExpandLong.java 无
float expand/ExpandFloat.java 无
double expand/ExpandDouble.java 无
string expand/ExpandString.java 无
minetweaker.item.IItemStack expand/ExpandItemStack.java 无
minecraft.item.IItemStack mods/ic2/expand/ItemExpansion.java:22 @ModOnly("IC2")
minetweaker.item.IIngredient 4 处合成(见下) 1 处带 @ModOnly("IC2")

minetweaker.item.IIngredient 由 4 个类分别贡献成员,属累加设计:

声明文件 行 门控
api/item/IngredientTransform.java :11 无
api/item/IngredientCondition.java :12 无
api/tooltip/IngredientTooltips.java :15 无
mods/ic2/expand/IngredientExpansion.java :21 @ModOnly("IC2")

minecraft.item.IItemStack 扩展(IC2 的 @ModOnly,ItemExpansion.java:21-22)只在装了 IC2 时生效,为原生 ItemStack 补 getChargeLevel() 等成员。

ExpandItemStack 等 11 个 expand/ 类同时带 @ZenCaster(expand/ExpandByte.java:20 等),让脚本能把原生 byte / int / ItemStack 等隐式转成脚本类型。

缺陷:WeightedLiquidStack 标注了错误的脚本名

minetweaker/api/liquid/WeightedLiquidStack.java:12 写的是:

@ZenClass("minetweaker.item.WeightedItemStack")

与 minetweaker/api/item/WeightedItemStack.java:12 完全同名(grep -rn 'minetweaker.item.WeightedItemStack' 命中且仅命中这两个文件)。后果:

  1. 脚本里没有 minetweaker.liquid.WeightedLiquidStack 这个名字。
  2. 两个类抢同一个 root 键,而 registerNativeClass 不做重复检查(GlobalRegistry.java:94),静默后注册者胜出。
  3. 谁先谁后由构建期扫描顺序决定:RegisterZenClassesTask.iterate 用 dir.listFiles()(buildSrc/src/main/java/minetweaker/tasks/RegisterZenClassesTask.java:106),Java 规范不保证该数组顺序。因此该名到底绑到物品版还是流体版加权栈,在不同机器/不同构建上可能不同。

缺陷:Java 类名与脚本名不一致的 4 处

脚本作者按 ZenScript 名字写代码时容易踩:

Java 类 脚本名 位置
PlayerCraftedEvent minetweaker.event.PlayerCraftingEvent api/event/PlayerCraftedEvent.java:11-12
IEventHandle minetweaker.event.IEventHandler api/event/IEventHandle.java:12
WeightedLiquidStack minetweaker.item.WeightedItemStack 见上节
IClient minetweaker.api.IClient api/client/IClient.java:11

其中 IClient 的前缀风格也与其余 60 余个 minetweaker.<子包>.<接口> 命名不同(多了一层 api.)。另 vanilla.* 4 个类型(IVanilla、ILootRegistry、ISeedRegistry、LootEntry)完全不带 minetweaker. 前缀。

未注册的接口(12 个)

api/ 下有 12 个接口没有 @ZenClass,因此类型不进命名空间:

接口 影响的全局对象
api/event/IEventManager.java events
api/formatting/IFormatter.java format
api/compat/IJEIRecipeRegistry.java NEI/IEI 配方查询
api/mods/IMod.java loadedMods 元素类型
api/recipes/IFurnaceRecipe.java furnace.all 元素类型
api/recipes/IMTRecipe.java —
api/data/IDataConverter.java NBT 转换
api/entity/IEntityItem.java 掉落物实体
api/damage/IDamageSource.java —
api/resource/IResourceManager.java —
api/vanilla/IVillagerRegistry.java —
api/world/IBiome.java game.biomes 元素类型

IEventManager 行首锚定注解数为 0(grep -cE '^\s*@' api/event/IEventManager.java → 0),而它 20 个 on* 方法也全部没有 @ZenMethod(api/event/IEventManager.java:15-50)。同类中 IEventHandle 有 @ZenClass + @ZenMethod close(),CommandValidators 有 @ZenClass + @ZenGetter isOp(),说明省略是逐个决定的而非全接口风格。

无法核实:events.onPlayerCrafted(...)、format.color(...) 这类调用在脚本中究竟是否可用,取决于外部 ZenScript 引擎对未注册 Java 类型的方法解析策略。ZenScript 1.0.2-GTNH 的源码不在本仓(仅 buildSrc 有注解与文档生成器的副本),我无法在本仓内确认其行为。已核实的是本仓侧的注册事实:这些接口未被 registerClass 处理。

相关条目