附魔(Enchantments)
附魔(Enchantments)是一种可以应用到工具及其他物品上的特殊效果。从 1.21 版本开始,附魔被作为 数据组件 储存在物品上,通过 JSON 文件进行定义,并由所谓的附魔效果组件(enchantment effect components)组成。在游戏运行时,某个物品上的所有附魔会被包含在 DataComponents.ENCHANTMENTS 组件中,以 ItemEnchantments 实例的形式存在。
你可以通过在自己命名空间(namespace)下的 enchantment 数据包子文件夹中创建一个 JSON 文件来添加新的附魔。例如,若要创建一个名为 examplemod:example_enchant 的附魔,需要新建一个文件 data/examplemod/enchantment/example_enchantment.json。
附魔 JSON 格式(Enchantment JSON Format)
{
// 此文本组件将作为附魔在游戏内显示的名称。
// 可以是一个翻译键(translation key)或直接的字符串。
// 如果使用翻译键,记得在你的语言文件中添加相应翻译!
"description": {
"translate": "enchantment.examplemod.enchant_name"
},
// 指定该附魔可以应用于哪些物品。
// 可以是一个物品 id,例如 "minecraft:trident",
// 也可以是物品 id 列表,如 ["examplemod:red_sword", "examplemod:blue_sword"],
// 或者是物品标签(item tag),例如 "#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"],
// 或附魔标签(enchantment tag),如 "#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 group)中生效。
// 槽组为 EquipmentSlotGroup 枚举的可能值之一。
// 原版槽组包括:`any`、`hand`、`mainhand`、`offhand`、`armor`、`feet`、`legs`、`chest`、`head` 和 `body`。
"slots": [
"mainhand"
],
// 此附魔所提供效果的映射(map),以附魔效果组件(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)。这个等级会在游戏中以绿色数字显示在附魔菜单的附魔旁边。对于每个附魔,基础等级会根据物品的附魔性(即 IItemExtension#getEnchantmentValue() 方法返回值)被随机调整两次,方式如下:
(修改后等级) = (基础等级) + 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 组件会根据其数据内容,改变武器造成的伤害数值。
原版 Minecraft 定义了多种 [内置附魔效果组件][built-in-enchantment-effect-components],所有原版附魔都是通过这些组件实现的。
自定义附魔效果组件(Custom Enchantment Effect Components)
自定义附魔效果组件的逻辑需要由开发者完全自行实现。首先,你需要定义一个类或 record(原文)来保存实现某种效果所需的信息。例如,下面是一个示例 record 类 Increment:
// 定义一个包含数据的示例 record。
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;
}
}
附魔效果组件类型必须被 注册 到 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,它是一个函数式接口,接收一个附魔和其等级,并会对给定物品堆(itemstack)上所有附魔依次调用(本质上是一个 BiConsumer<Holder<Enchantment>, Integer>)。
实际进行数值调整时,使用提供的 Increment#add 方法。由于这里是在 lambda 表达式中操作,我们需要使用像 AtomicInteger 这样的可原子更新的类型来修改数值。这样也允许同一个物品上存在多个 INCREMENT 组件并叠加它 们的效果,就像原版(vanilla)中的表现一样。
ConditionalEffect
将类型包装在 ConditionalEffect<?> 中,可以让附魔效果组件根据给定的 战利品上下文 有选择地生效。
ConditionalEffect 提供了 ConditionalEffect#matches(LootContext context) 方法,它会根据内部的 Optional<LootItemCondition> 判断该效果是否应当生效,并自动处理其 LootItemCondition 的序列化与反序列化。
原版(vanilla)还提供了一个额外的辅助方法,以进一步简化条件判断流程:Enchantment#applyEffects()。该方法接收一个 List<ConditionalEffect<T>>,评估每个条件,并对条件成立的每个 ConditionalEffect 中包含的 T 执行一个 Consumer<T>。由于许多原版附魔效果组件都被定义为 List<ConditionalEffect<?>>,因此可以像下面这样直接用于该辅助方法:
```java
// `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> 的编解码器(codec),以及某个 ContextKeySet 条目。
附魔数据生成(Enchantment Data Generation)
可以通过将 RegistrySetBuilder 传递给 DatapackBuiltInEntriesProvider,并在 GatherDataEvent#createDatapackRegistryObjects 中使用 数据生成 系统,自动生成附魔的 JSON 文件。生成的 JSON 文件会被放置在 <project root>/src/generated/data/<modid>/enchantment/<path>.json 路径下。
关于 RegistrySetBuilder 和 DatapackBuiltinEntriesProvider 的具体工作方式,请参阅 数据包注册表的数据生成 相关文章。
- Datagen
- 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(
// 指定附魔名称的文本组件(Component)。
Component.literal("Example Enchantment"),
// 指定我们附魔的附魔定义(enchantment definition)。
new Enchantment.EnchantmentDefinition(
// 一个包含该附魔兼容物品的 HolderSet。
HolderSet.direct(...),
// 一个可选的(Optional)HolderSet,表示该附魔认为是“主物品”的集合。
Optional.empty(),
// 附魔的权重。
30,
// 该附魔的最大等级。
3,
// 附魔的最小消耗。第一个参数为基础消耗,第二个为每级消耗。
new Enchantment.Cost(3, 1),
// 附魔的最大消耗。同上。
new Enchantment.Cost(4, 2),
// 附魔在铁砧上的消耗。
2,
// 该附魔生效的 EquipmentSlotGroup(装备槽组)列表。
List.of(EquipmentSlotGroup.ANY)
),
// 一个包含所有不兼容附魔的 HolderSet。
HolderSet.empty(),
// 一个 DataComponentMap,包含此附魔相关的附魔效果组件及其对应的值。
DataComponentMap.builder()
.set(MY_ENCHANTMENT_EFFECT_COMPONENT_TYPE, new ExampleData())
.build()
)
)
);
// 关于每个条目的详细说明,请参阅上方关于附魔 JSON 格式的章节。
{
// 附魔的铁砧消耗。
"anvil_cost": 2,
// 指定附魔名称的文本组件(Component)。
"description": "示例附魔",
// 与此附魔相关联的效果组件(effect components)及其数值的映射表。
"effects": {
// <effect components>
},
// 附魔的最大消耗。
"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": /* <supported item list> */,
// 此附魔的权重(weight)。
"weight": 30
}
数据组件 编解码器(Codec) 附魔定义相关的 Minecraft Wiki 页面 已注册 谓词(Predicate) 数据生成 用于数据包注册表的数据生成 相关 Minecraft Wiki 页面 内置附魔效果组件 LootContext