跳到主要内容
版本:1.21.6 - 1.21.8

附魔

附魔是可以应用于工具和其他物品的特殊效果。从 1.21 开始,附魔作为[数据组件]存储在物品上,通过 JSON 定义,并由所谓的附魔效果组件构成。在游戏中,特定物品上的附魔包含在 DataComponents.ENCHANTMENTS 组件内,在一个 ItemEnchantments 实例中。

可以通过在你的命名空间的 enchantment 数据包子文件夹中创建一个 JSON 文件来添加新的附魔。例如,要创建一个名为 examplemod:example_enchant 的附魔,需要创建文件 data/examplemod/enchantment/example_enchantment.json

附魔 JSON 格式

{
// 将用作附魔游戏内名称的文本组件。
// 可以是翻译键或字面字符串。
// 如果使用翻译键,请记得在你的语言文件中翻译它!
"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, inf)。
"anvil_cost": 2,

// (可选) 此附魔在其中提供效果的槽位组列表。
// 槽位组定义为 EquipmentSlotGroup 枚举的可能值之一。
// 在原版中,这些是:`any`、`hand`、`mainhand`、`offhand`、`armor`、`feet`、`legs`、`chest`、`head` 和 `body`。
"slots": [
"mainhand"
],

// 此附魔提供的效果,作为附魔效果组件的映射(继续阅读)。
"effects": {
"examplemod:custom_effect": [
{
"effect": {
"type": "minecraft:add",
"value": {
"type": "minecraft:linear",
"base": 1,
"per_level_above_first": 1
}
}
}
]
}
}

附魔成本与等级

max_costmin_cost 字段指定了创建此附魔所需附魔能量的边界。然而,实际上使用这些值的过程有些复杂。

首先,附魔台会考虑周围方块的 IBlockExtension#getEnchantPowerBonus() 返回值。基于此,它调用 EnchantmentHelper#getEnchantmentCost 来为每个槽位推导出一个“基础等级”。这个等级在游戏中显示为菜单中附魔旁边的绿色数字。对于每个附魔,基础等级会被一个由物品的附魔能力(从 DataComponents#ENCHANTABLE 数据组件通过 Enchantable#value 提取的返回值)推导出的随机值修改两次,如下所示:

(修改后的等级) = (基础等级) + random.nextInt(e / 4 + 1) + random.nextInt(e / 4 + 1),其中 e 是附魔能力值。

这个修改后的等级再随机上下调整 15%,最后用于选择附魔。此等级必须落在你的附魔成本边界内才能被选中。

实际上,这意味着你附魔定义中的成本值可能超过 30,有时远超过。例如,对于一个附魔能力为 10 的物品,附魔台可能会产生成本高达 1.15 * (30 + 2 * (10 / 4) + 1) = 40 的附魔。

附魔效果组件

附魔效果组件是特殊注册的[数据组件],用于决定附魔如何运作。组件的类型定义其效果,而它包含的数据则用于告知或修改该效果。例如,minecraft:damage 组件根据其数据修改武器造成的伤害。

原版定义了各种[内置附魔效果组件],用于实现所有原版附魔。

自定义附魔效果组件

自定义附魔效果组件的应用逻辑必须完全由其创建者实现。首先,你应该定义一个类或记录来保存实现给定效果所需的信息。例如,让我们创建一个示例记录类 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;
}
}

附魔效果组件类型必须在 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<?> 包装类型允许附魔效果组件根据给定的[战利品上下文]选择性地生效。

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 条目。

附魔数据生成

附魔 JSON 文件可以使用[数据生成]系统通过将 RegistrySetBuilder 传递给 DatapackBuiltInEntriesProvider(通过 GatherDataEvent#createDatapackRegistryObjects)自动创建。JSON 将放置在 <project root>/src/generated/data/<modid>/enchantment/<path>.json

有关 RegistrySetBuilderDatapackBuiltinEntriesProvider 如何工作的更多信息,请参阅关于[数据包注册表的数据生成]的文章。

// 有关每个条目的更多细节,请查看上面关于附魔 JSON 格式的部分。
{
// 附魔的铁砧成本。
"anvil_cost": 2,

// 指定附魔名称的文本组件。
"description": "示例附魔",

// 与此附魔关联的效果组件及其值的映射。
"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
}