附魔
附魔是可应用到工具等物品上的特殊效果。从 1.21 起,附魔以数据组件存储在物品上,由 JSON 定义,并由附魔效果组件构成。游戏中,特定物品的附魔位于 DataComponentTypes.ENCHANTMENT 组件中的 ItemEnchantment 实例。
在自己命名空间的 enchantment 数据包子目录创建 JSON,即可添加附魔。例如,原文给出的 examplemod:example_enchant 示例文件为 data/examplemod/enchantment/example_enchantment.json。
附魔 JSON 格式
{
// The text component that will be used as the in-game name of the enchantment.
// Can be a translation key or a literal string.
// Remember to translate this in your lang file if you use a translation key!
"description": {
"translate": "enchantment.examplemod.enchant_name"
},
// Which items this enchantment can be applied to.
// Can be either an item id, such as "minecraft:trident",
// or a list of item ids, such as ["examplemod:red_sword", "examplemod:blue_sword"]
// or an item tag, such as "#examplemod:enchantable/enchant_name".
// Note that this doesn't cause the enchantment to appear for these items in the enchanting table.
"supported_items": "#examplemod:enchantable/enchant_name",
// (Optional) Which items this enchantment appears for in the enchanting table.
// Can be an item, list of items, or item tag.
// If left unspecified, this is the same as `supported_items`.
"primary_items": [
"examplemod:item_a",
"examplemod:item_b"
],
// (Optional) Which enchantments are incompatible with this one.
// Can be an enchantment id, such as "minecraft:sharpness",
// or a list of enchantment ids, such as ["minecraft:sharpness", "minecraft:fire_aspect"],
// or enchantment tag, such as "#examplemod:exclusive_to_enchant_name".
// Incompatible enchantments will not be added to the same item by vanilla mechanics.
"exclusive_set": "#examplemod:exclusive_to_enchant_name",
// The likelihood that this enchantment will appear in the Enchanting Table.
// Bounded by [1, 1024].
"weight": 6,
// The maximum level this enchantment is allowed to reach.
// Bounded by [1, 255].
"max_level": 3,
// The maximum cost of this enchantment, measured in "enchanting power".
// This corresponds to, but is not equivalent to, the threshold in levels the player needs to meet to bestow this enchantment.
// See below for details.
// The actual cost will be between this and the min_cost.
"max_cost": {
"base": 45,
"per_level_above_first": 9
},
// Specifies the minimum cost of this enchantment; otherwise as above.
"min_cost": {
"base": 2,
"per_level_above_first": 8
},
// The cost that this enchantment adds to repairing an item in an anvil in levels. The cost is multiplied by enchantment level.
// If an item has a DataComponentTypes.STORED_ENCHANTMENTS component, the cost is halved. In vanilla, this only applies to enchanted books.
// Bounded by [1, inf).
"anvil_cost": 2,
// (Optional) A list of slot groups this enchantment provides effects in.
// A slot group is defined as one of the possible values of the EquipmentSlotGroup enum.
// In vanilla, these are: `any`, `hand`, `mainhand`, `offhand`, `armor`, `feet`, `legs`, `chest`, `head`, and `body`.
"slots": [
"mainhand"
],
// The effects that this enchantment provides as a map of enchantment effect components (read on).
"effects": {
"examplemod:custom_effect": [
{
"effect": {
"type": "minecraft:add",
"value": {
"type": "minecraft:linear",
"base": 1,
"per_level_above_first": 1
}
}
}
]
}
}
附魔成本和等级
max_cost 与 min_cost 规定生成附魔所需“附魔能力”的范围,实际使用过程较复杂。
附魔台先读取周围方块的 IBlockExtension#getEnchantPowerBonus(),再调用 EnchantmentHelper#getEnchantmentCost 得到每个槽位的基础等级,即界面中附魔旁的绿色数字。随后依据物品附魔能力(IItemExtension#getEnchantmentValue() 的返回值)产生两次随机修正:
修正等级 = 基础等级 + random.nextInt(e / 4 + 1) + random.nextInt(e / 4 + 1),其中 e 是物品附魔能力。
该结果再随机上下浮动 15%,最终用于选择附魔;只有落在附魔成本范围内的附魔才会被选中。
因此,附魔定义的成本可能高于 30,甚至高出很多。例如,附魔能力为 10 的物品可达到 1.15 * (30 + 2 * (10 / 4) + 1) = 40 的成本。
附魔效果组件
附魔效果组件是专门注册的数据组件,决定附魔行为。组件类型定义效果,所含数据提供或调整效果信息。例如,minecraft:damage 按照数据指定的数值修改武器伤害。
原版定义了多种内置效果组件,用来实现全部原版附魔。
自定义效果组件
自定义组件的应用逻辑必须由作者完整实现。先定义类或 record 保存效果所需信息,例如 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 对象的类型:
// In some registration class
public static final DeferredRegister<DataComponentType<?>> ENCHANTMENT_COMPONENT_TYPES = DeferredRegister.create(BuiltInRegistries.ENCHANTMENT_EFFECT_COMPONENT_TYPE, "examplemod");
public static final DeferredHolder<DataComponentType<?>, DataComponentType<Increment>>> INCREMENT =
ENCHANTMENT_COMPONENT_TYPES.register("increment",
() -> DataComponentType.<Increment>builder()
.persistent(Increment.CODEC)
.build());
接着实现使用组件修改整数的游戏逻辑:
// Somewhere in game logic where an `itemStack` is available.
// `INCREMENT` is the enchantment component type holder defined above.
// `value` is an integer.
AtomicInteger atomicValue = new AtomicInteger(value);
EnchantmentHelper.runIterationOnItem(stack, (enchantmentHolder, enchantLevel) -> {
// Acquire the Increment instance from the enchantment holder (or null if this is a different enchantment)
Increment increment = enchantmentHolder.value().effects().get(INCREMENT.get());
// If this enchant has an Increment component, use it.
if(increment != null){
atomicValue.set(increment.add(atomicValue.get()));
}
});
int modifiedValue = atomicValue.get();
// Use the now-modified value elsewhere in your game logic.
首先调用 EnchantmentHelper#runIterationOnItem 的某个重载。它接受 EnchantmentHelper.EnchantmentVisitor 函数接口,并以附魔及等级为参数,对物品堆的全部附魔执行操作(本质上类似 BiConsumer<Enchantment, Integer>)。
使用 Increment#add 执行修改。因为位于 lambda 内,需要 AtomicInteger 等可原子更新的类型。这样同一物品上的多个 INCREMENT 组件也能依次叠加效果,与原版行为类似。
ConditionalEffect
用 ConditionalEffect<?> 包装类型,可根据给定 LootContext 决定是否生效。
ConditionalEffect#matches(LootContext context) 依据内部 Optional<LootItemConditon> 判断能否运行,并处理 LootItemCondition 的序列化与反序列化。
原版的 Enchantment#applyEffects() 进一步简化了条件检查:它接受 List<ConditionalEffect<T>>,检查每个条件,再对通过条件的 T 执行 Consumer<T>。许多原版组件本身就是这种列表,可直接使用:
// `enchant` is an Enchantment instance.
// `lootContext` is a LootContext instance.
Enchantment.applyEffects(
enchant.getEffects(EnchantmentEffectComponents.KNOCKBACK), // Or whichever other List<ConditionalEffect<T>> you want
lootContext,
(effectData) -> // Use the effectData (in this example, an EnchantmentValueEffect) however you want.
);
自定义的 ConditionalEffect 包装组件可这样注册:
public static final DeferredHolder<DataComponentType<?>, DataComponentType<ConditionalEffect<Increment>>> CONDITIONAL_INCREMENT =
ENCHANTMENT_COMPONENT_TYPES.register("conditional_increment",
() -> DataComponentType.ConditionalEffect<Increment>builder()
// The LootContextParamSet needed depends on what the enchantment is supposed to do.
// This might be one of ENCHANTED_DAMAGE, ENCHANTED_ITEM, ENCHANTED_LOCATION, ENCHANTED_ENTITY, or HIT_BLOCK
// since all of these bring the enchantment level into context (along with whatever other information is indicated).
.persistent(ConditionalEffect.codec(Increment.CODEC, LootContextParamSets.ENCHANTED_DAMAGE))
.build());
ConditionalEffect.codec 的参数依次是泛型 ConditionalEffect<T> 对应的 codec,以及 LootContextParamSets 中的一项。
附魔数据生成
把 RegistrySetBuilder 传给 DatapackBuiltInEntriesProvider,即可通过数据生成创建附魔 JSON,输出到 <project root>/src/generated/data/<modid>/enchantment/<path>.json。
关于 RegistrySetBuilder 和 DatapackBuiltinEntriesProvider,见数据包注册表的数据生成。
数据生成
// This RegistrySetBuilder should be passed into a DatapackBuiltinEntriesProvider in your GatherDataEvent handler.
RegistrySetBuilder BUILDER = new RegistrySetBuilder();
BUILDER.add(
Registries.ENCHANTMENT,
bootstrap -> bootstrap.register(
// Define the ResourceKey for our enchantment.
ResourceKey.create(
Registries.ENCHANTMENT,
ResourceLocation.fromNamespaceAndPath("examplemod", "example_enchantment")
),
new Enchantment(
// The text Component that specifies the enchantment's name.
Component.literal("Example Enchantment"),
// Specify the enchantment definition of for our enchantment.
new Enchantment.EnchantmentDefinition(
// A HolderSet of Items that the enchantment will be compatible with.
HolderSet.direct(...),
// An Optional<HolderSet> of items that the enchantment considers "primary".
Optional.empty(),
// The weight of the enchantment.
30,
// The maximum level this enchantment can be.
3,
// The minimum cost of the enchantment. The first parameter is base cost, the second is cost per level.
new Enchantment.Cost(3, 1),
// The maximum cost of the enchantment. As above.
new Enchantment.Cost(4, 2),
// The anvil cost of the enchantment.
2,
// A list of EquipmentSlotGroups that this enchantment has effects in.
List.of(EquipmentSlotGroup.ANY)
),
// A HolderSet of incompatible other enchantments.
HolderSet.empty(),
// A DataComponentMap of the enchantment effect components associated with this enchantment and their values.
DataComponentMap.builder()
.set(MY_ENCHANTMENT_EFFECT_COMPONENT_TYPE, new ExampleData())
.build()
)
)
);
JSON
// For more detail on each entry, please check the section above on the enchantment JSON format.
{
// The anvil cost of the enchantment.
"anvil_cost": 2,
// The text Component that specifies the enchantment's name.
"description": "Example Enchantment",
// A map of the effect components associated with this enchantment and their values.
"effects": {
// <effect components>
},
// The maximum cost of the enchantment.
"max_cost": {
"base": 4,
"per_level_above_first": 2
},
// The maximum level this enchantment can be.
"max_level": 3,
// The minimum cost of the enchantment.
"min_cost": {
"base": 3,
"per_level_above_first": 1
},
// A list of EquipmentSlotGroup aliases that this enchantment has effects in.
"slots": [
"any"
],
// The set of items that this enchantment can be applied to using an anvil.
"supported_items": /* <supported item list> */,
// The weight of this enchantment.
"weight": 30
}
来源: NeoForged/Documentation(1.21.1)。中文翻译依据提交
4a680cdd4d15。