概述

在Minecraft中,体会到Mod之间的相互协作,正是Minecraft游戏的乐趣之一。这自然也包括自己的Mod本身,向其他Mod提供相应的API用于让其他Mod调用。本部分通过提供自定义金铁炉子的配方API的方式,向读者一步一步展示一个API,通常情况下是如何形成的。

API包

大部分情况下,Mod都将其的API放在一个特定的包里。这里我们采用com.github.ustc_zzzz.fmltutor.api包。我们新建这样的一个包,然后新建我们的package-info.java文件。众所周知,package-info.java这一文件通常用于描述一个包的相关信息,我们很快就可以知道这个文件在Mod之间具体的作用。

src/main/java/com/github/ustc_zzzz/fmltutor/api/package-info.java:

/**
 * @author ustc_zzzz
 */
@net.minecraftforge.fml.common.API(apiVersion = "0.1.0", owner = "fmltutor", provides = "FMLTutorAPI")
package com.github.ustc_zzzz.fmltutor.api;

实际上,和其他的package-info.java相比,我们只为其添加了一个@API注解。这个注解用于通知FML这个包内包含有一个Mod的API:

  • apiVersion代表这个API的版本
  • owner代表这个API的所属Mod,应与Mod的唯一标识符(这里是fmltutor)一致
  • provides代表这个API的名称,不同的API应该不相同,名称对大小写等不做限制,不过通常是Mod名称和“API”这三个字母组成

在上一节中我们提到过,虽然我们不推荐把API的代码Shade进自己的Mod,但是有的时候,有一些API,比如说cofh.api包下的Redstone Flux API——对很多Mod都是十分必要的,虽然说这个API是由cofhlib这一Mod提供的,但是在很多情况下运行该Mod的时候根本没有cofhlib的存在——因此所有使用到Redstone Flux API的Mod不得不Shade一份Redstone Flux API的二进制文件,以保证自己的Mod可以正常使用。

在这种情况下,Forge会注意到一个整合包里的所有Mod,包含有很多份Redstone Flux API的二进制代码——也就是cofh.api包下的代码出现了很多次。为了解决这一问题,FML会检查添加了@API注解的包对应的package-info,并注意到CoFHAPI这一API出现了多次,那么FML便只会选择一份API加载,从而避免了潜在的冲突。

总而言之,由于一份API可能在Mod列表中出现多次包含,通过@API注解,FML保证相应的API只会加载一份。

具体实现

我们在其下新建一个FMLTutorRecipeManager抽象类:

src/main/java/com/github/ustc_zzzz/fmltutor/api/FMLTutorRecipeManager.java:

package com.github.ustc_zzzz.fmltutor.api;

import net.minecraft.item.ItemStack;

public abstract class FMLTutorRecipeManager
{
    public abstract void addRecipe(ItemStack input, ItemStack output);

    public abstract ItemStack getResult(ItemStack input);
}

这个抽象类的两个抽象方法本身很简单,在此不再赘述。

我们在com.github.ustc_zzzz.fmltutor包下新建一个FMLTutorRecipeManagerImpl类,作为这个类的一个实现:

src/main/java/com/github/ustc_zzzz/fmltutor/FMLTutorRecipeManagerImpl.java:

package com.github.ustc_zzzz.fmltutor;

import com.github.ustc_zzzz.fmltutor.api.FMLTutorRecipeManager;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.FurnaceRecipes;

import java.util.LinkedList;

public class FMLTutorRecipeManagerImpl extends FMLTutorRecipeManager
{
    public static final FMLTutorRecipeManagerImpl INSTANCE = new FMLTutorRecipeManagerImpl();

    private final LinkedList<Recipe> recipes = new LinkedList<Recipe>();

    @Override
    public void addRecipe(ItemStack input, ItemStack output)
    {
        this.recipes.addFirst(new Recipe(input, output));
    }

    @Override
    public ItemStack getResult(ItemStack input)
    {
        for (Recipe recipe : recipes)
        {
            if (recipe.input.isItemEqual(input))
            {
                return recipe.output;
            }
        }
        return FurnaceRecipes.instance().getSmeltingResult(input);
    }

    private static class Recipe
    {
        private final ItemStack input;
        private final ItemStack output;

        private Recipe(ItemStack input, ItemStack output)
        {
            this.input = input;
            this.output = output;
        }
    }
}

这个实现本身很简单,这里也不加赘述了。我们这里添加了一个INSTANCE字段用于稍后在API中获取实现:

src/main/java/com/github/ustc_zzzz/fmltutor/api/FMLTutorRecipeManager.java(部分):

    public static final FMLTutorRecipeManager INSTANCE;

    static
    {
        try
        {
            Class<?> implClass = Class.forName("com.github.ustc_zzzz.fmltutor.FMLTutorRecipeManagerImpl");
            INSTANCE = (FMLTutorRecipeManager) implClass.getDeclaredField("INSTANCE").get(null);
        }
        catch (Exception e)
        {
            throw new RuntimeException("Cannot find implementation", e);
        }
    }

这里使用了一点简单的Java反射API,用于获取该字段,对于读者来说应该不难理解。对反射API不熟悉的读者可以先行阅读附录里的相关内容。

然后我们修改一下金属炉子的相关代码:

src/main/java/com/github/ustc_zzzz/fmltutor/tileentity/TileEntityMetalFurnace.java(部分):

                // previous code: ItemStack furnaceRecipeResult = FurnaceRecipes.instance().getSmeltingResult(itemStack);
                ItemStack furnaceRecipeResult = FMLTutorRecipeManager.INSTANCE.getResult(itemStack);

启动游戏,一切依旧正常运转。

打包

我们如果想要让自己的Mod的API供其他Mod使用,就要提供相应的JAR文件——实际上,我们可以通过Gradle很轻松地生成这一文件。我们在build.gradle文件下加入以下代码:

build.gradle:

task apiJar(type: Jar) {
    classifier = 'api'
    from sourceSets.main.output
    from sourceSets.main.allSource
    include 'com/github/ustc_zzzz/fmltutor/api/**'
}

artifacts {
    archives apiJar
}

上面这段代码定义了一个生成JAR的Task:apiJarclassifier用于设置最后生成文件的后缀,这里将导致最后生成fmltutor-1.0.0-api.jar文件。紧随其后的两句from开头的语句指定Gradle分别从生成的字节码二进制文件和源代码中提取文件,而最后的include开头的语句指定Gradle提取com.github.ustc_zzzz.fmltutor.api包下的所有文件。

最后的以artifacts开头的部分代码指定我们需要使用apiJar这一Task生成JAR。因此,如果不出所料,在运行相应的构建语句,比如(针对Linux或者Mac OS X):

./gradlew build

或者(针对Microsoft Windows):

gradlew.bat build

之后的build/libs目录下应该同时会生成fmltutor-1.0.0-api.jar这一文件。

这个JAR文件目前应该只包含五个文件:

  • META-INF/MANIFEST.MF
  • com/github/ustc_zzzz/fmltutor/api/package-info.class
  • com/github/ustc_zzzz/fmltutor/api/FMLTutorRecipeManager.class
  • com/github/ustc_zzzz/fmltutor/api/package-info.java
  • com/github/ustc_zzzz/fmltutor/api/FMLTutorRecipeManager.java

这同时包含了编译后的二进制和源代码,应该是我们想要看到的。

其他Mod只需要引用这一JAR,就可以完成和我们编写的Mod的交互了。

读者也可以只单独执行这一Task,比如(针对Linux或者Mac OS X):

./gradlew apiJar

或者(针对Microsoft Windows):

gradlew.bat apiJar

最后将只生成fmltutor-1.0.0-api.jar这一文件。

results matching ""

    No results matching ""