本篇教程的视频

本篇教程的源代码

Github地址:TutorialMod-ArmorEffect-1.21

介绍

(本篇教程和18-1的叠加盔甲效果合并了)

在一些模组里面,当玩家穿戴全套盔甲时,会获得一些额外的效果,比如抗火保护迅捷等等

这期教程我们就来制作这样的效果。当然,你也可以实现当玩家穿上某个特定的盔甲(饰品)时,也可以获得一些效果;不过本期视频还是按照全套盔甲来实现

这里我们首先得了解,前面写的盔甲是实例化原版的类,但是原版没有提供这样的效果,所以我们需要自己去写一个盔甲的类,然后在这个类中去实现我们的效果

创建ModArmorItem类

首先我们创建一个ModArmorItem

因为我们写的是盔甲,可以继承ArmorItem,这样我们就不用从一无所有的Item写起了,并在这个类中实现我们的效果

1
2
3
4
5
6
public class ModArmorItem extends ArmorItem {

public ModArmorItem(RegistryEntry<ArmorMaterial> material, Type type, Settings settings) {
super(material, type, settings);
}
}

super函数也别忘了

设计逻辑

那么现在我们来想想怎么去实现它

玩家的物品栏是不是有专门的盔甲栏,也就是我们在GUI中看到的小人边上的那四个槽

我们能不能通过这些槽去判断玩家是否穿了全套的盔甲?当然可以!

然后我们就可以遍历实时这些槽,判断玩家是否穿的是同一套材质的全套盔甲,并给予玩家一定的效果

实时遍历物品栏,这个在Item类中是inventoryTick方法,可以重写。我们也不希望这个效果是一次性的(即穿上以后到时间就没效果了),我们希望一直穿着也就一直有这个效果(这个就要实时判断了,到时间了立刻续上),当玩家脱下后,效果到时间了也就自然没了。

另外,我们还得注意,鞘翅这个东西也可以被放到盔甲栏中,但是它不是盔甲类物品,也没有盔甲材质,所以在写的时候还得单独拎出来判断

实现逻辑

重写inventoryTick

1
2
3
4
5
6
7
@Override
public void inventoryTick(ItemStack stack, World world, Entity entity, int slot, boolean selected) {
if (!world.isClient() && entity instanceof PlayerEntity player && hasFullSuitOfArmor(player)) {
evaluateArmorEffects(player);
}
super.inventoryTick(stack, world, entity, slot, selected);
}

其实重写这个方法的语句很简单,之后我们得创建一系列的辅助方法

这里的处理逻辑在服务端处理,并且当实体是玩家实体且玩家穿了全套盔甲(自定义方法hasFullSuitOfArmor(player),其实就是遍历那四个物品栏)时,给予玩家效果(自定义方法evaluateArmorEffects(player)

evaluateArmorEffects方法

在给予玩家效果之前,我们还得写个Map,用于存储对应的盔甲材料和效果

1
2
3
4
5
6
private static final Map<ArmorMaterial, List<StatusEffectInstance>> MAP =
(new ImmutableMap.Builder<ArmorMaterial, List<StatusEffectInstance>>())
.put(ModArmorMaterials.ICE_ETHER.value(),
Arrays.asList(new StatusEffectInstance(StatusEffects.FIRE_RESISTANCE,1000, 1, false,false,true),
new StatusEffectInstance(StatusEffects.SPEED, 1000, 1, false,false,true)))
.build();

我这里直接以MAP命名了,如果你写了多个不同材质的盔甲,或者说你想写原版的材料,就单独再写这样类似的语句

不过,要想让原版的盔甲也有这种增益效果,是得写Mixin的,毕竟原版实例化的是它自己的盔甲类

这里我们写的是ModArmorMaterials.ICE_ETHER对应多个效果,用List来存放我们的效果,写一个也是可以的

StatusEffectInstance的参数是效果持续时间等级是否是显示氛围(我不太确定是什么)、是否是显示粒子是否是显示图标,这里我写的是抗火保护迅捷,持续时间是1000tick,等级是1

然后我们再来写evaluateArmorEffects方法

1
2
3
4
5
6
7
8
9
10
11
12
private void evaluateArmorEffects(PlayerEntity player) {
for (Map.Entry<ArmorMaterial, List<StatusEffectInstance>> entry : MAP.entrySet()) {
ArmorMaterial material = entry.getKey();
List<StatusEffectInstance> effects = entry.getValue();

if (hasCorrectArmorSet(player, material)) {
for (StatusEffectInstance effect : effects) {
addStatusEffectForMaterial(player, effect);
}
}
}
}

这里我们遍历MAP,然后判断玩家是否穿了对应的盔甲(自定义方法hasCorrectArmorSet(player, material),这里是判断材质,对应材质给效果),如果是,就给予玩家效果(自定义方法addStatusEffectForMaterial(player, effect)

这里的效果也得遍历出来,因为我们是用List存放的,所以我们得遍历这个List,然后给予玩家效果

hasCorrectArmorSet方法

这个方法是判断玩家是否穿了对应材质的盔甲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private boolean hasCorrectArmorSet(PlayerEntity player, ArmorMaterial material) {
for (ItemStack stack : player.getInventory().armor) {
if (!(stack.getItem() instanceof ArmorItem)) {
return false;
}
}

ArmorItem helmet = (ArmorItem) player.getInventory().getArmorStack(3).getItem();
ArmorItem chestplate = (ArmorItem) player.getInventory().getArmorStack(2).getItem();
ArmorItem leggings = (ArmorItem) player.getInventory().getArmorStack(1).getItem();
ArmorItem boots = (ArmorItem) player.getInventory().getArmorStack(0).getItem();

return helmet.getMaterial().value() == material &&
chestplate.getMaterial().value() == material &&
leggings.getMaterial().value() == material &&
boots.getMaterial().value() == material;
}

根据我们前面所说,鞘翅可以放在盔甲栏中,所以我们得先判断这个槽中的物品是否是盔甲类物品的实例,如果不是,就返回false。后面我们要获取盔甲的材质,但是鞘翅是没有的,不判断的话游戏会崩溃

这里的getArmorStack里面填的是那些盔甲栏的索引,3是头盔,2是胸甲,1是护腿,0是靴子,是自下而上的,得注意一下

下面就是获取玩家盔甲栏中的物品,然后判断是否是对应的材质,如果是,就返回true,否则返回false

addStatusEffectForMaterial方法

1
2
3
4
5
6
7
private void addStatusEffectForMaterial(PlayerEntity player, StatusEffectInstance effect) {
boolean hasEffect = player.hasStatusEffect(effect.getEffectType());

if (!hasEffect) {
player.addStatusEffect(new StatusEffectInstance(effect));
}
}

这个方法是给予玩家效果,首先判断玩家是否有这个效果,如果没有,就给予玩家这个效果

hasFullSuitOfArmor方法

现在我们回上来,写hasFullSuitOfArmor方法

1
2
3
4
5
6
7
8
private boolean hasFullSuitOfArmor(PlayerEntity player) {
ItemStack helmet = player.getInventory().getArmorStack(3);
ItemStack chestplate = player.getInventory().getArmorStack(2);
ItemStack leggings = player.getInventory().getArmorStack(1);
ItemStack boots = player.getInventory().getArmorStack(0);

return !helmet.isEmpty() && !chestplate.isEmpty() && !leggings.isEmpty() && !boots.isEmpty();
}

这个方法是判断玩家是否穿了全套盔甲,如果四个槽中的物品都不为空,就返回true,否则返回false

同样的,这里的getArmorStack和前面一样,注意一下

完整代码

方法稍微有点多,这里我把完整的代码贴出来

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class ModArmorItem extends ArmorItem {
private static final Map<ArmorMaterial, List<StatusEffectInstance>> MAP =
(new ImmutableMap.Builder<ArmorMaterial, List<StatusEffectInstance>>())
.put(ModArmorMaterials.ICE_ETHER.value(),
Arrays.asList(new StatusEffectInstance(StatusEffects.FIRE_RESISTANCE,1000, 1, false,false,true),
new StatusEffectInstance(StatusEffects.SPEED, 1000, 1, false,false,true)))
.build();

public ModArmorItem(RegistryEntry<ArmorMaterial> material, Type type, Settings settings) {
super(material, type, settings);
}

@Override
public void inventoryTick(ItemStack stack, World world, Entity entity, int slot, boolean selected) {
if (!world.isClient() && entity instanceof PlayerEntity player && hasFullSuitOfArmor(player)) {
evaluateArmorEffects(player);
}
super.inventoryTick(stack, world, entity, slot, selected);
}

private void evaluateArmorEffects(PlayerEntity player) {
for (Map.Entry<ArmorMaterial, List<StatusEffectInstance>> entry : MAP.entrySet()) {
ArmorMaterial material = entry.getKey();
List<StatusEffectInstance> effects = entry.getValue();

if (hasCorrectArmorSet(player, material)) {
for (StatusEffectInstance effect : effects) {
addStatusEffectForMaterial(player, effect);
}
}
}
}

private void addStatusEffectForMaterial(PlayerEntity player, StatusEffectInstance effect) {
boolean hasEffect = player.hasStatusEffect(effect.getEffectType());

if (!hasEffect) {
player.addStatusEffect(new StatusEffectInstance(effect));
}
}

private boolean hasCorrectArmorSet(PlayerEntity player, ArmorMaterial material) {
for (ItemStack stack : player.getInventory().armor) {
if (!(stack.getItem() instanceof ArmorItem)) {
return false;
}
}

ArmorItem helmet = (ArmorItem) player.getInventory().getArmorStack(3).getItem();
ArmorItem chestplate = (ArmorItem) player.getInventory().getArmorStack(2).getItem();
ArmorItem leggings = (ArmorItem) player.getInventory().getArmorStack(1).getItem();
ArmorItem boots = (ArmorItem) player.getInventory().getArmorStack(0).getItem();

return helmet.getMaterial().value() == material &&
chestplate.getMaterial().value() == material &&
leggings.getMaterial().value() == material &&
boots.getMaterial().value() == material;
}

private boolean hasFullSuitOfArmor(PlayerEntity player) {
ItemStack helmet = player.getInventory().getArmorStack(3);
ItemStack chestplate = player.getInventory().getArmorStack(2);
ItemStack leggings = player.getInventory().getArmorStack(1);
ItemStack boots = player.getInventory().getArmorStack(0);

return !helmet.isEmpty() && !chestplate.isEmpty() && !leggings.isEmpty() && !boots.isEmpty();
}
}

总体上就是这个样子,就一些重复性的语句比较多

重新注册盔甲

那么现在,既然我们已经写好了自己的盔甲类,我们就得拿它来实例化,才能实现我们的效果

1
2
3
public static final Item ICE_ETHER_HELMET = registerItems("ice_ether_helmet", 
new ModArmorItem(ModArmorMaterials.ICE_ETHER, ArmorItem.Type.HELMET,
new Item.Settings().maxDamage(ArmorItem.Type.HELMET.getMaxDamage(37))));

改其中的一个就好了,比如我这里改了头盔。因为我们自定义的盔甲类是遍历另外三个的,其本身也是继承ArmorItem的,也属于ArmorItem的实例,所以改一个就好了

其他的东西我们就不用改了,还是按前一篇教程的来

随后我们进入游戏,穿好全套的盔甲,就可以看到我们的效果了。我们在设置的时候显示了图标,我们就能在右上角看到我们的效果