附魔(Enchantments)
附魔是可以应用于工具和其他物品的特殊效果。自1.21起,附魔作为数据组件存储在物品上,在JSON中定义,并由所谓的附魔效果组件组成。在游戏中,特定物品上的附魔包含在DataComponents.ENCHANTMENTS组件中的ItemEnchantments实例内。
可以通过在命名空间的enchantment数据包子文件夹中创建JSON文件来添加新附魔。例如,要创建名为examplemod:example_enchant的附魔,需要创建文件data/examplemod/enchantment/example_enchantment.json。
附魔JSON格式(Enchantment JSON Format)
{
// 将用作附魔游戏内名称的文本组件。
// 可以是翻译键或字面字符串。
// 如果使用翻译键,请记得在您的语言文件中翻译它!
"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,
// (可选)此附魔提供效果的槽位组列表。
// 槽位组定义为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
}
}
}
]
}
}
附魔成本和等级(Enchantment Costs and Levels)
max_cost和min_cost字段指定创建此附魔所需的附魔力量边界。然而,实际使用这些值的过程有些复杂。
首先,附魔台考虑周围方块的IBlockExtension#getEnchantPowerBonus()返回值。由此,它调用EnchantmentHelper#getEnchantmentCost来推导每个槽位的"基础等级"。此等级在游戏中显示为菜单中附魔旁边的绿色数字。对于每个附魔,基础等级通过从物品附魔能力(其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)
附魔效果组件是特殊注册的数据组件,决定附魔如何运作。组件的类型定义其效果,而其包含的数据用于通知或修改该效果。例如,minecraft:damage组件根据其数据确定的量修改武器造成的伤害。
原版定义了各种内置附魔效果组件,用于实现所有原版附魔。
自定义附魔效果组件(Custom Enchantment Effect Components)
应用自定义附魔效果组件的逻辑必须由其创建者完全实现。首先,您应该定义一个类或记录来保存实现给定效果所需的信息。例如,让我们创建一个示例记录类Increment:
// Define an example data-bearing 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,这是一个接受附魔及其等级的函数式接口,并在给定物品堆栈具有的所有附魔上调用(本质上是一个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条目。
附魔数据生成(Enchantment Data Generation)
附魔JSON文件可以使用数据生成系统自动创建,通过将RegistrySetBuilder传递到DatapackBuiltInEntriesProvider中,通过GatherDataEvent#createDatapackRegistryObjects。JSON将放置在<project root>/src/generated/data/<modid>/enchantment/<path>.json。
有关RegistrySetBuilder和DatapackBuiltinEntriesProvider如何工作的更多信息,请参阅数据包注册表的数据生成文章。
- 数据生成
- JSON
// 此RegistrySetBuilder应传递到您的`GatherDataEvent`s侦听器中的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.literal("Example Enchantment"),
// 为我们的附魔指定附魔定义。
new Enchantment.EnchantmentDefinition(
// 附魔将兼容的物品的HolderSet。
HolderSet.direct(...),
// 附魔视为"主要"的物品的可选HolderSet。
Optional.empty(),
// 附魔的权重。
30,
// 此附魔可以达到的最大等级。
3,
// 附魔的最小成本。第一个参数是基础成本,第二个是每级成本。
new Enchantment.Cost(3, 1),
// 附魔的最大成本。同上。
new Enchantment.Cost(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,
// 指定附魔名称的文本组件。
"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
}