本篇教程的视频

本篇教程的源代码

GitHub地址:TutorialMod-Enchantment-1.21

本篇教程目标

  • 学会添加附魔

前言

来到1.21之后,附魔也变成数据驱动的了

所以,其实你完全可以按照数据包的写法来写附魔,叠几个附魔在一起

高版本很多东西都改成数据驱动的了,这对数据包开发来说是好事,但对于模组开发那可不太好

不过,如果要实现具体的附魔效果,比如像精准采集、火焰附加、荆棘等这种对工具属性进行改变的,
还是得通过代码的形式来添加

我们这里呢,源代码就不看了,具体思路是编写一个附魔类型,注册它,
再用数据生成来生成附魔相关的一些内容,比如等级、消耗的经验、可以附加在那些东西上

源代码我提一下,Enchantments是原版的注册类,其中有个bootstrap方法,用于数据生成

net.minecraft.enchantment.effects包下,放了原版那些附魔的类型

一些作用在实体上的附魔效果,比如迅捷火焰附加等,这些是实现EnchantmentEntityEffect接口的

附魔有些效果并没有单独的类,所以研究起来可能有一点困难,有些是直接在注册时,
addEffect方法添加的效果,所以具体的源代码我就不提了

本篇教程参考Fabric的官方文档:Enchantments

关于工具的附魔

1.21之前,如果我们添加自定义的工具,是可以附魔并有实际效果的

但在1.21之后,,因为附魔变成数据驱动的缘故,工具还要给他们写Tag才能在附魔台上附魔并产生效果

这里,我们先来写工具那些Tag的数据生成

我们到ModItemTagsProvider类中加上

1
2
3
4
5
6
7
8
getOrCreateTagBuilder(ItemTags.SWORD_ENCHANTABLE)
.add(ModItems.FIRE_ETHER_SWORD);

getOrCreateTagBuilder(ItemTags.MINING_ENCHANTABLE)
.add(ModItems.FIRE_ETHER_PICKAXE)
.add(ModItems.FIRE_ETHER_HOE)
.add(ModItems.FIRE_ETHER_SHOVEL)
.add(ModItems.FIRE_ETHER_AXE);

剑和其他开采用工具是分开写的

因为属于武器,而其他是属于采集方块的工具,有些可以用在剑上的附魔是不能用在开采类工具上的

然后跑个数据生成,再进入游戏进行测试,我们之前写的这些工具就可以在附魔台上附魔了

自定义附魔

那么,回到我们这篇教程的重点来,来添加我们自定义的附魔

创建自定义附魔

这里我们来创建一个TestEnchantmentEffect,按照原版,实现EnchantmentEntityEffect接口

还要注意的是,这个类我们写成record类,与原版一致,当然这里我们没什么参数,就空着好了

1
2
3
4
5
6
7
8
9
10
11
12
public record TestEnchantmentEffect() implements EnchantmentEntityEffect {

@Override
public void apply(ServerWorld world, int level, EnchantmentEffectContext context, Entity user, Vec3d pos) {

}

@Override
public MapCodec<? extends EnchantmentEntityEffect> getCodec() {
return null;
}
}

实现EnchantmentEntityEffect要我们重写两个方法,其中一个是CODEC,是编解码器
这个是高版本都有的一个东西,用于数据传输的

所以这里我们得写一个

1
public static final MapCodec<TestEnchantmentEffect> CODEC = MapCodec.unit(TestEnchantmentEffect::new);

因为我们没有一些额外的参数,所以就写一个简单的编解码器就可以

然后在下面的getCodec方法中返回

1
2
3
4
@Override
public MapCodec<? extends EnchantmentEntityEffect> getCodec() {
return CODEC;
}

而后,我们回过头来看apply方法,这个方法用来指定这个附魔会有什么效果

那么,参考Fabric的文档,我们就按照不同等级给它不同的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (level == 1) {
EntityType.LIGHTNING_BOLT.spawn(world, user.getBlockPos(), SpawnReason.TRIGGERED);
}

if (level == 2) {
EntityType.LIGHTNING_BOLT.spawn(world, user.getBlockPos(), SpawnReason.TRIGGERED);
EntityType.LIGHTNING_BOLT.spawn(world, user.getBlockPos(), SpawnReason.TRIGGERED);
}

if (level == 3) {
EntityType.LIGHTNING_BOLT.spawn(world, user.getBlockPos(), SpawnReason.TRIGGERED);
EntityType.LIGHTNING_BOLT.spawn(world, user.getBlockPos(), SpawnReason.TRIGGERED);
EntityType.LIGHTNING_BOLT.spawn(world, user.getBlockPos(), SpawnReason.TRIGGERED);
}

比较简单,也可以说有点草率

这些代码就是生成闪电LIGHTNING_BOLT

spawn方法有三个参数,第一个是当前世界,第二个是生成闪电的位置,第三个是生成闪电的原因

当然,能做文章的东西很多,这里既然能生成闪电,那么也可以生成其他实体,
另外也可以结合其他的方法,比如药水效果粒子等内容,制作更为复杂的附魔效果

注册自定义附魔

这里我们再创建一个ModEnchantments类,用于自定义附魔的注册

1
2
3
public class ModEnchantments {

}

首先是一个注册方法,用原版的方法再加上我们的命名空间

1
2
3
private static RegistryKey<Enchantment> of(String id) {
return RegistryKey.of(RegistryKeys.ENCHANTMENT, Identifier.of(TutorialMod.MOD_ID, id));
}

然后注册附魔的注册键

1
public static final RegistryKey<Enchantment> TEST = of("test");

那么接下来就是bootstrap方法,用于数据生成

1
2
3
public static void bootstrap(Registerable<Enchantment> registry) {

}

里面的参数直接从原版搬过来就可以

另外还要一个注册方法,用在数据生成里的注册方法

1
2
3
private static void register(Registerable<Enchantment> registry, RegistryKey<Enchantment> key, Enchantment.Builder builder) {
registry.register(key, builder.build(key.getValue()));
}

然后我们就可以在bootstrap方法中注册了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
RegistryEntryLookup<Enchantment> registryEntryLookup2 = registry.getRegistryLookup(RegistryKeys.ENCHANTMENT);
RegistryEntryLookup<Item> registryEntryLookup3 = registry.getRegistryLookup(RegistryKeys.ITEM);

register(registry, TEST, Enchantment.builder(Enchantment.definition(
registryEntryLookup3.getOrThrow(ItemTags.WEAPON_ENCHANTABLE),
registryEntryLookup3.getOrThrow(ItemTags.SWORD_ENCHANTABLE),
5,
3,
Enchantment.leveledCost(5, 7),
Enchantment.leveledCost(25, 10),
2,
AttributeModifierSlot.MAINHAND))
.exclusiveSet(registryEntryLookup2.getOrThrow(EnchantmentTags.DAMAGE_EXCLUSIVE_SET))
.addEffect(EnchantmentEffectComponentTypes.POST_ATTACK,
EnchantmentEffectTarget.ATTACKER, EnchantmentEffectTarget.VICTIM,
new TestEnchantmentEffect())
);

首先是两个RegistryEntryLookup,这个就直接从原版搬过来的

我们重点来看注册方法中的definition方法

它有8个参数

第一个是支持附魔的物品,第二个是主要支持的附魔物品

第三个是权重,即在附魔台出现的概率

第四个是最大等级

第五个是每个等级附魔台所需的最小经验消耗,第六个是每个等级附魔台所需的最大经验消耗
leveledCost的两个参数,第一个是基础消耗,第二个是每级递增的消耗

第七个是铁砧附魔消耗的经验(附魔书直接附魔)

第八个是生效的槽位,一般为主手生效

上面的这些参数可以在后面生成的数据文件中找到

exclusiveSet方法是指定附魔所在的集合,因为附魔和药水效果不同,有些附魔是不能同时存在
这里的DAMAGE_EXCLUSIVE_SET是其中一个伤害类附魔集合

addEffect方法是指定附魔造成的效果,第一个参数是附魔的组件类型,可以理解为何时附魔生效,
POST_ATTACK攻击后生效,还有其他很多种,比如DAMAGE攻击造成伤害时生效等等

第二个参数和第三个参数分别是攻击方被攻击方注意顺序,不然按照我们上面的附魔效果,写反了就是我们被雷劈了

最后一个参数就是我们的自定义附魔了,实例化它即可

数据生成类调用

我们到TutorialModDataGenerator中来调用bootstrap方法

1
registryBuilder.addRegistry(RegistryKeys.ENCHANTMENT, ModEnchantments::bootstrap);

另外,在ModWorldGen中,也要加上对ENCHANTMENT注册键的动态注册

1
entries.addAll(registries.getWrapperOrThrow(RegistryKeys.ENCHANTMENT));

编解码器注册

实际上,在EnchantmentEntityEffect接口中,还注册了很多编解码器

所以我们在自定义附魔类中写的编解码器也是要进行注册的

这里我们再创建一个ModEnchantmentEffects类,用来注册编解码器

1
2
3
public class ModEnchantmentEffects {

}

首先写一个注册方法

1
2
3
private static MapCodec<? extends EnchantmentEntityEffect> register(String name, MapCodec<? extends EnchantmentEntityEffect> codec) {
return Registry.register(Registries.ENCHANTMENT_ENTITY_EFFECT_TYPE, Identifier.of(TutorialMod.MOD_ID, name), codec);
}

这个也是从原版的EnchantmentEntityEffect中搬过来的,再加上我们的命名空间

然后注册我们的编解码器

1
public static final MapCodec<? extends EnchantmentEntityEffect> TEST = register("test", TestEnchantmentEffect.CODEC);

另外,万变不离其宗的,一个用于初始化的注册方法

1
2
3
public static void registerModEnchantmentEffects() {

}

再在模组主类中调用这个方法

1
ModEnchantmentEffects.registerModEnchantmentEffects();

然后我们就可以跑数据生成,而后,我们就能在data/tutorialmod/enchantment目录下看到我们的附魔的json文件了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
"anvil_cost": 2,
"description": {
"translate": "enchantment.tutorialmod.test"
},
"effects": {
"minecraft:post_attack": [
{
"affected": "victim",
"effect": {
"type": "tutorialmod:test"
},
"enchanted": "attacker"
}
]
},
"exclusive_set": "#minecraft:exclusive_set/damage",
"max_cost": {
"base": 25,
"per_level_above_first": 10
},
"max_level": 3,
"min_cost": {
"base": 5,
"per_level_above_first": 7
},
"primary_items": "#minecraft:enchantable/sword",
"slots": [
"mainhand"
],
"supported_items": "#minecraft:enchantable/weapon",
"weight": 5
}

这里面的参数就是我们上面在bootstrap方法中写的