标签类型与合并
计算器里流转的每一个「格子」都是一个 ILabel。本 mod 定义了 4 种标签类型
和一张 5 条的合并规则表。
基本信息
| 属性 | 值 |
|---|---|
| 接口 | me.towdium.jecalculation.data.label.ILabel |
| 标签类型数 | 4(LItemStack / LFluidStack / LOreDict / LPlaceholder) |
| 空标签 | ILabel.EMPTY,实现类是内部类 LEmpty,IDENTIFIER = "empty" |
| 合并表条目数 | 5 |
| 颜色常量 | FORMAT_BLUE = "\u00A79"、FORMAT_GREY = "\u00A78"、FORMAT_ITALIC = "\u00A7o" |
4 种标签类型
| 类 | IDENTIFIER |
承载内容 | acceptPercent() |
|---|---|---|---|
LItemStack |
"itemStack" |
Item + meta + NBTTagCompound nbt + fMeta(模糊 meta)+ fNbt(模糊 NBT) |
true(继承 Impl) |
LFluidStack |
"fluidStack" |
FluidStack(amount 以 mB 计) |
false(唯一覆写为 false 的真实类型) |
LOreDict |
"oreDict" |
矿物词典名 name(如 ingotIron) |
true(继承 Impl) |
LPlaceholder |
"placeholder" |
一个字符串 name(占位名称,如 "example")+ 数量 |
true(继承 Impl) |
4 个真实类型都 extends ILabel.Impl。Impl 本身不实现 acceptPercent(),
而是提供默认实现 return true;只有 LFluidStack 显式覆写成 false,
空标签 LEmpty 也覆写成 false。所以流体标签不能切到百分比模式。
LItemStack 的 NBT 键(均为 private static final):
| 键 | 对应字段 |
|---|---|
"item" |
Item item |
"meta" |
int meta |
"tag" |
NBTTagCompound nbt(源码注释 // don't modify it) |
"fMeta" |
boolean fMeta |
"fNbt" |
boolean fNbt |
fMeta 为真时构造器会把 meta 归零(this.meta = fMeta ? 0 : meta),实现「忽略 meta」。
tooltip 会据此追加灰色提示行 label.item_stack.fuzzy_meta / label.item_stack.fuzzy_nbt,
并追加一行蓝色斜体的 mod 名。
IDENTIFIER 会被序列化进 NBT 的 "type" 键(ILabel.Serializer.KEY_IDENTIFIER = "type"),
因此这 4 个字符串是存档格式的一部分,改动会导致旧存档无法读取。
合并规则表(5 条)
ILabel.MERGER 在 initClient() 中注册:
| # | 左类型 | 右类型 | 合并函数 | 语义 |
|---|---|---|---|---|
| 1 | itemStack |
itemStack |
LItemStack::merge |
同一物品(含 meta/NBT 宽松规则)→ 数量相加 |
| 2 | oreDict |
oreDict |
LOreDict::mergeSame |
词典名完全相等 才合并 |
| 3 | oreDict |
itemStack |
LOreDict::mergeFuzzy |
模糊匹配:物品属于该词典且符号相反 |
| 4 | fluidStack |
fluidStack |
LFluidStack::merge |
同一流体 → 数量相加 |
| 5 | placeholder |
placeholder |
LPlaceholder::merge |
同一占位名 → 数量相加 |
没有 itemStack → oreDict 方向的注册。反向的合并会由框架尝试调换参数顺序
后重新查表(Merger.merge 的注释:
For different type, the framework will try reversing the order for MergeFunctions to work. So generally speaking, a and b has no priority in this function
)——即 merge(itemStack, oreDict) 会退化成查 merge(oreDict, itemStack),即规则 3。
Merger.merge 用 functions.get(a.getIdentifier(), b.getIdentifier()) 查表,
查不到就返回 Optional.empty(),即「不可合并」。
LOreDict.mergeSame(规则 2)
if (a instanceof LOreDict && b instanceof LOreDict) {
return ((LOreDict) a).getName().equals(((LOreDict) b).getName());
}
return false;
只比 name,不比数量。
LOreDict.mergeFuzzy(规则 3)——符号检查是硬门槛
if (lod.getAmount() * lis.getAmount() < 0) {
for (ItemStack ore : OreDictionary.getOres(lod.name)) {
if (LItemStack.merge(Converter.from(ore), lis)) return true;
}
}
return false;
两个关键条件:
- 数量乘积必须为负(一正一负)。源码里带着
// TODO check performance注释—— 这里每次都要遍历OreDictionary.getOres(name)的完整列表。 - 必须能被
LItemStack.merge逐个匹配上。
也就是说:同号(两个都是需求,或两个都是富余)的标签不会被模糊合并。 这与 代价列表算法 里
positive => generate; negative => require的符号约定配合,使得「富余的铁锭」不会被并进「需求的矿物词典铁锭」。
数量与百分比
每个标签都同时携带 amount(long)和 isPercent()(读 Impl.percent 布尔)。
基类 Impl 的 NBT 键是 KEY_AMOUNT = "amount" 与 KEY_PERCENT = "percent"。
| 表示 | percent |
界面按钮 | 在 Recipe.multiplier() 中 |
|---|---|---|---|
| 绝对数量 | false |
# |
先 × 100 |
| 百分比 | true |
% |
不缩放 |
切换百分比会同步改写数量,这是 Impl.setPercent 的实际算术:
public ILabel setPercent(boolean p) {
if (p && !acceptPercent()) throw new UnsupportedOperationException();
if (p && !percent) {
amount *= 100; // ← 绝对 → 百分比:数量乘 100
percent = true;
} else if (!p && percent) {
amount = (amount + 99) / 100; // ← 百分比 → 绝对:向上取整除以 100
percent = false;
}
return this;
}
(amount + 99) / 100是向上取整——所以 150% 会变成 2 而不是 1, 与 代价列表算法 中Recipe.multiplier的(amountB + amountA - 1) / amountB是同一种取整风格。 对acceptPercent()为false的类型(流体、空标签)切到百分比会抛UnsupportedOperationException;LEmpty.setPercent更是无条件抛。
Impl.multiply(float i)
float amount = i * getAmount();
if (amount > Long.MAX_VALUE) throw new ArithmeticException("Multiply overflow");
return setAmount((long) amount);
用 float 中转,溢出前就抛 ArithmeticException("Multiply overflow")。
Impl.equals
return obj instanceof Impl && amount == ((Impl) obj).amount && matches(obj);
数量必须完全相等才判等。这正是 代价列表算法 里
set.contains(result) 循环检测能成立的前提。
LPlaceholder.state 是一个全局静态开关,Controller.loadFromLocal() 在反序列化
前后临时把它置 true 再还原,让占位符能读出百分比信息。见 数据文件。
反序列化 / 选择器注册
ILabel.initClient() 还注册了两组转换器:
| 组 | 注册内容 | 优先级 |
|---|---|---|
CONVERTER |
LItemStack::suggest、LOreDict::suggest、LFluidStack::suggest |
Priority.SUGGEST |
CONVERTER |
LItemStack::fallback、LOreDict::fallback |
Priority.FALLBACK |
EDITOR |
PickerSimple.FluidStack::new,默认 new LFluidStack(1000, FluidRegistry.WATER) |
"fluid" |
EDITOR |
PickerSimple.OreDict::new,默认 new LOreDict("ingotIron") |
"ore" |
EDITOR |
PickerPlaceholder::new,默认 new LPlaceholder("example", 1, true) |
"placeholder" |
EDITOR |
PickerItemStack::new,默认 new LItemStack(new ItemStack(Items.iron_pickaxe)).setFMeta(true) |
"item" |
SUGGEST 优先于 FALLBACK:suggest 用于「帮玩家猜一个更宽松的标签」
(例如把一个铁锭物品标签建议成 oreDict),fallback 只在无建议时兜底。
配方消歧 里的建议浮层就是走这条 CONVERTER.guess 路径。
CONVERTER.Priority 与标签解析优先级
还有一个两值优先级枚举(ILabel.Converter.Priority):SUGGEST 与 FALLBACK。
它在 JecaOverlayHandler.merge() 里被间接使用:Recipe.IO.INPUT 且该槽
不止一个候选时,才调用 ILabel.CONVERTER.first(list, context) 去挑代表标签;
单个候选时直接用它自己。
相关条目
- 代价列表算法 -
mergeLabels/multiplyLabel如何转发到本层 - 配方消歧 -
SUGGEST/FALLBACK在界面上的表现 - 数据文件 - 4 种标签类型的序列化格式
- 本 mod 没有的东西 - 本 mod 不提供的数值维度