附魔(Enchantments)
附魔(Enchantments)是可以应用于工具和其他物品的特殊效果。从1.21开始,附魔作为[数据组件(Data Components)]存储在物品上,在JSON中定义,并由所谓的附魔效果组件组成。在游戏中,特定物品上的附魔包含在DataComponents.ENCHANTMENTS组件中,在ItemEnchantments实例中。
可以通过在命名空间的enchantment数据包子文件夹中创建JSON文件来添加新的附魔。例如,要创建名为examplemod:example_enchant的附魔,可以创建文件data/examplemod/enchantment/example_enchantment.json。
附魔JSON格式
{
// 将用作游戏中附魔名称的文本组件(Text Component)。
// 可以是翻译键或字面字符串。
// 如果使用翻译键,请记得在您的语言文件中翻译它!
"description": {
"translate": "enchantment.examplemod.enchant_name"
},
// 此附魔可以应用于哪些物品。
// 可以是物品ID,例如 "minecraft:trident",
// 或物品ID列表,例如 ["examplemod:red_sword", "examplemod:blue_sword"]
// 或物品标签,例如 "#examplemod:enchantable/enchant_name"。
// 注意:这不会导致该附魔在附魔台上为这些物品出现。
"supported_items": "#examplemod:enchantable/enchant_name",
// (可选) 此附魔在附魔台中为哪些物品出现。
// 可以是物品、物品列表或物品标签。
// 如果未指定,则与 `supported_items` 相同。
"primary_items": [
"examplemod:item_a",
"examplemod:item_b"
],
// (可选) 哪些附魔与此附魔不兼容。
// 可以是附魔ID,例如 "minecraft:sharpness",
// 或附魔ID列表,例如 ["minecraft:sharpness", "minecraft:fire_aspect"],
// 或附魔标签,例如 "#examplemod:exclusive_to_enchant_name"。
// 不兼容的附魔不会被原版机制添加到同一物品上。
"exclusive_set": "#examplemod:exclusive_to_enchant_name",
// 此附魔出现在附魔台中的可能性。
// 范围在 [1, 1024] 之间。
"weight": 6,
// 此附魔允许达到的最大等级。
// 范围在 [1, 255] 之间。
"max_level": 3,
// 此附魔的最大成本,以“附魔力量”衡量。
// 这对应于(但不等于)玩家需要满足的等级阈值才能赋予此附魔。
// 详情见下文。
// 实际成本将在此值和 min_cost 之间。
"max_cost": {
"base": 45,
"per_level_above_first": 9
},
// 指定此附魔的最小成本;其他同上。
"min_cost": {
"base": 2,
"per_level_above_first": 8
},
// 此附魔在铁砧中修复物品时增加的等级成本。成本乘以附魔等级。
// 如果物品具有 DataComponentTypes.STORED_ENCHANTMENTS 组件,则成本减半。在原版中,这仅适用于附魔书。
// 范围在 [1, 无穷大) 之间。
"anvil_cost": 2,
// (可选) 此附魔提供效果的槽位组(slot groups)列表。
// 槽位组定义为 EquipmentSlotGroup 枚举的可能值之一。
// 在原版中,这些是:`any`、`hand`、`mainhand`、`offhand`、`armor`、`feet`、`legs`、`chest`、`head` 和 `body`。
"slots": [
"mainhand"
],
// 此附魔提供的效果,作为附魔效果组件(enchantment effect components)的映射(继续阅读)。
"effects": {
"examplemod:custom_effect": [
{
"effect": {
"type": "minecraft:add",
"value": {
"type": "minecraft:linear",
"base": 1,
"per_level_above_first": 1
}
}
}
]
}
}
附魔成本与等级(Enchantment Costs and Levels)
max_cost 和 min_cost 字段指定了创建此附魔所需附魔力量(enchanting power)的边界。然而,实际使用这些值的过程有些复杂。
首先,附魔台会考虑周围方块的 IBlockExtension#getEnchantPowerBonus() 返回值。由此,它调用 EnchantmentHelper#getEnchantmentCost 来为每个槽位推导出一个“基础等级(base level)”。这个等级在游戏中显示为菜单中附魔旁边的绿色数字。对于每个附魔,基础等级会通过一个随机值被修改两次,该随机值源自物品的附魔能力(enchantability)(通过 Enchantable#value 从物品的 DataComponents#ENCHANTABLE 数据组件中提取的返回值),如下所示:
(修改后的等级) = (基础等级) + random.nextInt(e / 4 + 1) + random.nextInt(e / 4 + 1),其中 e 是附魔能力分数。
这个修改后的等级会随机上下调整15%,然后最终用于选择附魔。这个等级必须落在你附魔定义的成本范围内才能被选中。
实际上,这意味着你附魔定义中的成本值可能高于30,有时甚至远高于30。例如,对于一个附魔能力为10的物品,附魔台可能产生成本高达 1.15 * (30 + 2 * (10 / 4) + 1) = 40 的附魔。
附魔效果组件(Enchantment Effect Components)
附魔效果组件是特殊注册的[数据组件(Data Components)],用于确定附魔的功能。组件的类型定义了其效果,而其包含的数据则用于告知或修改该效果。例如,minecraft:damage 组件根据其数据修改武器造成的伤害。
原版定义了各种[内置附魔效果组件(built-in enchantment effect components)],用于实现所有原版附魔。
自定义附魔效果组件(Custom Enchantment Effect Components)
自定义附魔效果组件的应用逻辑必须完全由其创建者实现。首先,你应该定义一个类或记录(record)来保存实现给定效果所需的信息。例如,让我们创建一个示例记录类 Increment:
// 定 义一个示例数据承载记录。
public record Increment(int value) {
public static final Codec<Increment> CODEC = RecordCodecBuilder.create(instance ->
instance.group(
Codec.INT.fieldOf("value").forGetter(Increment::value)
).apply(instance, Increment::new)
);
public int add(int x) {
return value() + x;
}
}
附魔效果组件类型必须[注册(registered)]到 BuiltInRegistries.ENCHANTMENT_EFFECT_COMPONENT_TYPE,它接受一个 DataComponentType<?>。例如,你可以注册一个可以存储 Increment 对象的附魔效果组件,如下所示:
// 在某个注册类中
public static final DeferredRegister.DataComponents ENCHANTMENT_COMPONENT_TYPES =
DeferredRegister.createDataComponents(BuiltInRegistries.ENCHANTMENT_EFFECT_COMPONENT_TYPE, "examplemod");
public static final Supplier<DataComponentType<Increment>> INCREMENT =
ENCHANTMENT_COMPONENT_TYPES.registerComponentType(
"increment",
builder -> builder.persistent(Increment.CODEC)
);
现在,我们可以实现一些游戏逻辑,利用这个组件来修改整数值:
// 在游戏逻辑中的某个地方,有一个可用的 `itemStack`。
// `INCREMENT` 是上面定义的附魔组件类型持有者。
// `value` 是一个整数。
AtomicInteger atomicValue = new AtomicInteger(value);
EnchantmentHelper.runIterationOnItem(stack, (enchantmentHolder, enchantLevel) -> {
// 从附魔持有者获取 Increment 实例(如果这是不同的附魔,则为 null)
Increment increment = enchantmentHolder.value().effects().get(INCREMENT.get());
// 如果此附魔具有 Increment 组件,则使用它。
if(increment != null){
atomicValue.set(increment.add(atomicValue.get()));
}
});
int modifiedValue = atomicValue.get();
// 在你的游戏逻辑的其他地方使用现在已修改的值。
首先,我们调用 EnchantmentHelper#runIterationOnItem 的一个重载。该函数接受一个 EnchantmentHelper.EnchantmentVisitor,这是一个函数式接口,它接受一个附魔及其等级,并在给定物品堆栈拥有的所有附魔上调用(本质上是一个 BiConsumer<Holder<Enchantment>, Integer>)。
为了实际执行调整,使用提供的 Increment#add 方法。由于这是在 lambda 表达式内部,我们需要使用可以原子更新的类型,例如 AtomicInteger,来修改这个值。这也允许多个 INCREMENT 组件在同一物品上运行并叠加效果,就像原版中发生的那样。
ConditionalEffect
将类型包装在 ConditionalEffect<?> 中,允许附魔效果组件根据给定的[战利品上下文(LootContext)]选择性地生效。
ConditionalEffect 提供了 ConditionalEffect#matches(LootContext context),该方法根据其内部的 Optional<LootItemConditon> 返回效果是否应该运行,并处理其 LootItemCondition 的序列化和反序列化。
原版添加了一个额外的辅助方法,以进一步简化检查这些条件的过程:Enchantment#applyEffects()。此方法接收一个 List<ConditionalEffect<T>>,评估条件,并对每个满足条件的 ConditionalEffect 所包含的 T 运行一个 Consumer<T>。由于许多原版附魔效果组件被定义为 List<ConditionalEffect<?>>,因此可以直接将这些组件插入辅助方法,如下所示:
// `enchant` 是一个 Enchantment 实例。
// `lootContext` 是一个 LootContext 实例。
enchant.applyEffects(
// 或者任何其他你想要的 List<ConditionalEffect<T>>
enchant.getEffects(EnchantmentEffectComponents.KNOCKBACK),
// 用于测试条件的上下文
lootContext,
(effectData) -> // 以你想要的任何方式使用 effectData(在此示例中,是一个 EnchantmentValueEffect)。
);
注册自定义的 ConditionalEffect 包装的附魔效果组件类型可以按如下方式进行:
public static final DeferredHolder<DataComponentType<?>, DataComponentType<ConditionalEffect<Increment>>> CONDITIONAL_INCREMENT =
ENCHANTMENT_COMPONENT_TYPES.register("conditional_increment",
() -> DataComponentType.ConditionalEffect<Increment>builder()
// 所需的 ContextKeySet 取决于附魔应该做什么。
// 这可能是 ENCHANTED_DAMAGE、ENCHANTED_ITEM、ENCHANTED_LOCATION、ENCHANTED_ENTITY 或 HIT_BLOCK 之一
// 因为所有这些都将附魔等级带入上下文(以及指示的任何其他信息)。
.persistent(ConditionalEffect.codec(Increment.CODEC, LootContextParamSets.ENCHANTED_DAMAGE))
.build());
ConditionalEffect.codec 的参数是泛型 ConditionalEffect<T> 的编解码器,后跟某个 ContextKeySet 条目。
附魔数据生成(Enchantment Data Generation)
附魔 JSON 文件可以使用[数据生成(data generation)]系统自动创建,通过将 RegistrySetBuilder 传递给 DatapackBuiltInEntriesProvider(通过 GatherDataEvent#createDatapackRegistryObjects)。JSON 将放置在 <project root>/src/generated/data/<modid>/enchantment/<path>.json。
有关 RegistrySetBuilder 和 DatapackBuiltinEntriesProvider 如何工作的更多信息,请参阅关于[数据包注册表的数据生成(Data Generation for Datapack Registries)]的文章。
- 数据生成
- JSON
// 这个 RegistrySetBuilder 应该在你的 `GatherDataEvent` 监听器中传递给一个 DatapackBuiltinEntriesProvider。
RegistrySetBuilder BUILDER = new RegistrySetBuilder();
BUILDER.add(
Registries.ENCHANTMENT,
bootstrap -> bootstrap.register(
// 为我们的附魔定义 ResourceKey。
ResourceKey.create(
Registries.ENCHANTMENT,
ResourceLocation.fromNamespaceAndPath("examplemod", "example_enchantment")
),
new Enchantment(
// 指定附魔名称的文本组件(Text Component)。
Component.literal("Example Enchantment"),
// 为我们附魔的附魔定义(EnchantmentDefinition)。
new Enchantment.EnchantmentDefinition(
// 附魔将兼容的物品的 HolderSet。
HolderSet.direct(...),
// 附魔认为是“主要(primary)”物品的 Optional<HolderSet>。
Optional.empty(),
// 附魔的权重。
30,
// 此附魔可以达到的最大等级。
3,
// 附魔的最小成本。第一个参数是基础成本,第二个是每级成本。
Enchantment.dynamicCost(3, 1),
// 附魔的最大成本。同上。
Enchantment.dynamicCost(4, 2),
// 附魔的铁砧成本。
2,
// 此附魔有效果的 EquipmentSlotGroups 列表。
List.of(EquipmentSlotGroup.ANY)
),
// 不兼容的其他附魔的 HolderSet。
HolderSet.empty(),
// 与此附魔关联的附魔效果组件及其值的数据组件映射(DataComponentMap)。
DataComponentMap.builder()
.set(MY_ENCHANTMENT_EFFECT_COMPONENT_TYPE, new ExampleData())
.build()
)
)
);
// 关于每个条目的更多详细信息,请查看上面关于附魔JSON格式的部分。
{
// 附魔的铁砧成本。
"anvil_cost": 2,
// 指定附魔名称的文本组件(Text Component)。
"description": "Example Enchantment",
// 与此附魔关联的效果组件及其值的映射。
"effects": {
// <效果组件>
},
// 附魔的最大成本。
"max_cost": {
"base": 4,
"per_level_above_first": 2
},
// 此附魔可以达到的最大等级。
"max_level": 3,
// 附魔的最小成本。
"min_cost": {
"base": 3,
"per_level_above_first": 1
},
// 此附魔有效果的 EquipmentSlotGroup 别名列表。
"slots": [
"any"
],
// 可以使用铁砧应用此附魔的物品集合。
"supported_items": /* <支持的物品列表> */,
// 此附魔的权重。
"weight": 30
}