本篇教程的视频:

本篇教程源代码

GitHub地址:TutorialMod-RecipeRemainder-1.21

介绍

本篇教程我们将编写一个类似于IC2这种工业模组中工业锤的这类物品,这种物品在合成中不会直接消耗,而是合成后扣除一定的耐久度,直到耐久度为0时才会消失

这种特性在原版中没有物品用到,但有一个保留的方法,就是getRecipeRemainder,这个方法是用来指定合成后剩余的物品的

1
2
3
4
@Nullable
public final Item getRecipeRemainder() {
return this.recipeRemainder;
}

这个就是在Items中的方法,如果说我们要让物品具有上面描述的特性,就要重写这个方法

不过本篇教程将采用我群友提供的代码,因为根据Fabric Wiki上的方法,其实还存在一个不大不小的bug,这个我们等会再讲

耐久合成

创建AbstractDurabilityItem类

首先我们创建一个接口,这个接口继承FabricItem,这里这样写为的是修复我们上面提到的那个bug

1
2
3
4
5
6
7
8
9
10
11
public interface AbstractDurabilityItem extends FabricItem {
@Override
default ItemStack getRecipeRemainder(ItemStack stack) {
if (stack.getDamage() < stack.getMaxDamage() - 1) {
ItemStack itemStack = stack.copy();
itemStack.setDamage(itemStack.getDamage() + 1);
return itemStack;
}
return ItemStack.EMPTY;
}
}

这里我们重写了getRecipeRemainder方法,这个方法是在合成后剩余的物品

如果物品的耐久度小于最大耐久度 - 1,那么就返回一个当前耐久度 + 1的物品(这里的耐久度其实是物品的数据组件,其值越大,耐久度越低,因为我们的耐久度从0开始计算),否则返回一个空物品

改写FireEther类

我们改写之前为工具写材料时加的这个类,让它实现AbstractDurabilityItem接口

1
2
3
4
5
public class FireEther extends Item implements AbstractDurabilityItem {
public FireEther(Item.Settings settings) {
super(settings.maxDamage(128));
}
}

这里我们设置FireEther的最大耐久度为128,这个maxDamage可以在注册的时候写,也可以在这里写,一样的

注册什么的我们前面都已经写好了,这里就不再赘述

创建配方

我们虽然已经写了一个耐久合成的物品,但是没配方我们也没法合成,所以我们来创建一个配方

1
2
3
4
5
ShapelessRecipeJsonBuilder.create(RecipeCategory.MISC, ModItems.ANTHRACITE, 1)
.input(Items.COAL)
.input(ModItems.FIRE_ETHER)
.criterion("has_item", RecipeProvider.conditionsFromItem(Items.COAL))
.offerTo(exporter, Identifier.of(TutorialMod.MOD_ID, "anthracite"));

这里我们创建了一个无序合成,合成后得到ANTHRACITE,配方中需要COALFIRE_ETHER,这个FIRE_ETHER就是我们刚刚写的耐久合成物品

跑完数据生成之后,我们就可以启动游戏,测试我们的这个配方了

发现BUG

看起来没问题对吧,物品耐久度消耗完了才会消失

但是!假如说你将两个FIRE_ETHER放在一起,它可以合成一个FIRE_ETHER,但是那两个FIRE_ETHER的耐久度都会减少,并不会消失!因此你能靠两个FIRE_ETHER生产无穷无尽的FIRE_ETHER

这显然违背了能量守恒定律(虽然玩个游戏也不用这么较真,但确确实实的破坏了游戏的平衡)

这个Bug不大不小,源自游戏本身对于工具类物品特有的耐久合并

此时我们将FIRE_ETHER换成两个相同的,但是消耗了一定耐久度的工具,再给它进行合成,你会得到一个新的工具,而那两个工具没了

其机制可参见Wiki对于物品修复的解释

那么现在我们得来修这个bug,修复的方法有两个,均由群友提供

解决BUG

方法一——禁用耐久合并

这里我们要来改写RepairItemRecipe这个类,这个类是用来修复物品的

里面的方法也是Wiki上面提到的物品修复的方法

1
2
3
4
5
6
7
8
9
10
11
@Mixin(RepairItemRecipe.class)
public class RepairItemRecipeMixin {
@Inject(method = "findPair", at = @At("RETURN"), cancellable = true)
private void prevent(CraftingRecipeInput input, CallbackInfoReturnable<Pair<ItemStack, ItemStack>> cir) {
Pair<ItemStack, ItemStack> pair = cir.getReturnValue();
if (pair != null) {
Item item = pair.getFirst().getItem();
cir.setReturnValue((item.getDefaultStack().getRecipeRemainder() != null && item instanceof AbstractDurabilityItem) ? null: pair);
}
}
}

创建RepairItemRecipeMixin类,并将其添加到tutorialmod.mixin.json

这里我们修改了findPair方法,如果它的合成物品是我们的耐久合成物品,并且它的getRecipeRemainder不为空,那么就返回null,这样就禁用了耐久合并

上面我们使用接口将耐久合成的物品提取出来,为的就是在这里能够使用instanceof来判断,这样原版的那些工具依旧不受影响

这里可以看一下原作者对其解释

explain1

方法二——移除返还物

这里我们要来改写CraftingResultSlot这个类,这个类是用来返回合成物品的

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
@Mixin(CraftingResultSlot.class)
public class CraftingResultSlotMixin {
@Inject(method = "onTakeItem", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/recipe/RecipeManager;" +
"getRemainingStacks(Lnet/minecraft/recipe/RecipeType;Lnet/minecraft/recipe/input/RecipeInput;" +
"Lnet/minecraft/world/World;)Lnet/minecraft/util/collection/DefaultedList;"))
private void removeNonDurabilityTools(PlayerEntity player, ItemStack stack, CallbackInfo ci, @Local
LocalRef<DefaultedList<ItemStack>> defaultedListLocalRef) {
Item check = null;
DefaultedList<ItemStack> defaultedList = defaultedListLocalRef.get();
int length = defaultedList.size();
boolean isRepair = false;
for (ItemStack itemStack: defaultedList) {
if (itemStack.equals(ItemStack.EMPTY)) continue;
if (check == null) {
check = itemStack.getItem();
continue;
}
if (itemStack.isOf(check)) {
if (isRepair) {
isRepair = false;
break;
}
isRepair = true;
}
}
if (isRepair) {
defaultedListLocalRef.set(DefaultedList.ofSize(length, ItemStack.EMPTY));
}
}
}

这里的@Local会爆红,但不用管他,能跑就行

这个方法让我们的物品像原版的工具一样,支持耐久合并,但是不会出现bug

这里直接来看原作者的解释吧,我就不解释了

explain2

另外的注意事项见本篇教程对应的视频教程置顶评论原作者的回复