本篇教程的视频

本篇教程的源代码

GitHub地址:TutorialMod-FuelItem-1.21

介绍

燃烧物是一种特殊的物品,它可以作为游戏中熔炉、高炉等方块实体的燃料,用于烧炼各种物品

当然,除了物品,还有方块也可以作为燃烧物,比如各种原木,而它们本身是可以作为可燃物的(可以被点燃,火会蔓延)。不过,可燃物的教程我们将在添加自定义的木头这一篇教程中讲解

在这篇教程中,我会提供两种添加燃烧物的方法,一种是提供Fabric API的方法,另一种是使用Mixin的方法

为何使用Mixin

这里我们先讲解Mixin的方法,配合着前一篇教程的内容,我们可以很容易地添加燃烧物

查看源代码

不过在用Mixin之前,我们需要先查看源代码,找到我们需要的方法,然后再进行Mixin

我们查找Items中的一些燃烧物,以COAL为例

1
public static final Item COAL = register("coal", new Item(new Item.Settings()));

遗憾的是,这里的注册方法并没有提供燃烧时间,所以我们需要进一步查找

按住Ctrl键,点击COAL这个字段,我们可以查找所有引用这个字段的地方(范围为所有位置)

查找这些引用的地方也是有技巧的,首先看类名,再看后面的具体引用的语句,就可以缩小范围了

1
2
3
4
5
6
7
8
9
10
11
12
13
public static Map<Item, Integer> createFuelTimeMap() {
Map<Item, Integer> map = fuelTimes;
if (map != null) {
return map;
} else {
Map<Item, Integer> map2 = Maps.<Item, Integer>newLinkedHashMap();
...
addFuel(map2, Items.COAL, 1600);
...
fuelTimes = map2;
return map2;
}
}

我们找到AbstractFurnaceBlockEntity中的createFuelTimeMap方法,这个方法顾名思义,是用来创建燃烧时间的

在它的方法体中,我们可以看到addFuel(map2, Items.COAL, 1600);这一行,这就是添加燃烧物的方法

看到这里你应该知道为什么要用Mixin了,因为原版的燃烧物的燃烧时间是直接写在这个熔炉类中的,我们无法直接修改,然后我们再讲讲其他的

addFuel方法的参数是Map<Item, Integer> mapItem itemint fuelTime,我们可以看到COAL的燃烧时间是1600,单位自然是tick,那么对应80s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static void addFuel(Map<Item, Integer> fuelTimes, ItemConvertible item, int fuelTime) {
Item item2 = item.asItem();
if (isNonFlammableWood(item2)) {
if (SharedConstants.isDevelopment) {
throw (IllegalStateException)Util.throwOrPause(
new IllegalStateException(
"A developer tried to explicitly make fire resistant item " + item2.getName(null).getString() + " a furnace fuel. That will not work!"
)
);
}
} else {
fuelTimes.put(item2, fuelTime);
}
}

至于这其中的isNonFlammableWood方法对我们这期教程来说并不重要,感兴趣可以自行研究。我们现在只看else里的东西

结合着上面的addFuel(map2, Items.COAL, 1600);,不难理解,这个方法就是将COAL这个物品和它的燃烧时间1600放到fuelTimes这个Map

1
2
@Nullable
private static volatile Map<Item, Integer> fuelTimes;

这个fuelTimes是一个Map类型的一个私有字段

所以本质上吧,其实就是折腾这个Map类型的变量,所以我们直接使用Mixin来折腾这个Map就可以了

在不考虑使用API的情况下,我们得把我们自己的物品和燃烧时间塞到这个字段中(当然,你翻Fabric API的那个方法,也是Mixin,只是别人帮你把轮子造好了,咱们后面再说)

注册物品

在写Mixin或者使用Fabric API之前,我们先注册物品

注册

1
public static final Item ANTHRACITE = registerItems("anthracite", new Item(new Item.Settings()));

这里我们注册了一个ANTHRACITE物品,即无烟煤

加入物品栏

1
entries.add(ModItems.ANTHRACITE);

数据生成

语言文件

1
translationBuilder.add(ModItems.ANTHRACITE, "Anthracite");

模型文件

1
itemModelGenerator.register(ModItems.ANTHRACITE, Models.GENERATED);

然后记得跑一遍Data Generation,生成数据。当然,贴图也别忘了

Mixin

前面也讲过为什么要使用Mixin了,那么现在我们就来将燃烧物的注册方法塞到AbstractFurnaceBlockEntity

创建AbstractFurnaceBlockEntityMixin类

和目标类一样,我们将其声明为abstract

1
2
3
4
@Mixin(AbstractFurnaceBlockEntity.class)
public abstract class AbstractFurnaceBlockEntityMixin {

}

并记得将这个类加入到tutorialmod.mixins.json中(插件可以自动加入)

1
2
3
4
5
6
7
8
9
10
11
{
"required": true,
"package": "com.besson.tutorialmod.mixin",
"compatibilityLevel": "JAVA_21",
"mixins": [
"AbstractFurnaceBlockEntityMixin"
],
"injectors": {
"defaultRequire": 1
}
}

编写方法

随后,我们就开始写里面的方法

1
@Shadow private static volatile @Nullable Map<Item, Integer> fuelTimes;

这里我们使用@Shadow注解,为fuelTimes这个字段创建一个影子,这样我们就可以直接使用目标类中的这个字段了

1
2
3
4
@Inject(method = "createFuelTimeMap", at = @At("TAIL"))
private static void addFuelItems(CallbackInfoReturnable<Map<Item, Integer>> cir) {
fuelTimes.put(ModItems.ANTHRACITE, 1600);
}

而后,我们使用@Inject注解,创建addFuelItems方法,方法的参数由Minecraft Development插件修正

method是我们前面看到的那个方法,注入的位置at是这个方法的尾部TAIL(不要给它写在头部HEAD,一旦写在了头部就会导致原版其他的燃烧物失效了(你可以尝试一下,原因自行研究,教程视频中也讲了))

在这个方法下的语句就是要注入的代码,因为Map类型可以使用put方法,这里我们就直接塞我们的物品和时间

这样我们的物品就成为燃烧物了,我们可以启动游戏,只要没有出问题,我们就可以使用这个新的物品熔炼东西了

PS:其实这个Mixin类并不是很严谨,但能够实现我们所需。Mixin的写法多种多样,也可以去参考Fabric API里的那些方法

Fabric API

可能一些同学还是搞不清Mixin的东西,没关系,Fabric API早已提供了燃烧物的注册,这里我们就来使用

我们在主类初始化方法中加入以下语句即可

1
FuelRegistry.INSTANCE.add(ModItems.ANTHRACITE, 1600);

嗯,就这么简单。一个物品再加一个时间即可

而如果你想知道这背后到底是什么,那么可以挖一挖,它对应的实例类是FuelRegistryImpl,该类在Fabric的AbstractFurnaceBlockEntityMixin类中被调用