MC Fabric 1.20.1 学习笔记 —— 制作一个独立配方的工作台 08 Recipe和Serializer【完结】

252 阅读3分钟

前后端都写完了,接下来编写我们的数据,首先介绍我希望合成表应当是什么形式的结构,我们希望这个合成表是一种特殊的合成表,只能在之前编写的WorkBlock中形成。其次我们希望使用泥土+高草丛合成一个粘液球:

{
  "type": "modid:work_recipe",
  "input": [
    {
      "item": "minecraft:dirt"
    },
    {
      "item": "minecraft:tall_grass"
    }
  ],
  "resultItem": "minecraft:slime_ball",
  "resultCount": 1
}

这里type制定了配方类型,其指向序列化器WorkBlockRecipeSerializer的ID。
input是一个数组,其中包含一个item,这个input数组指向一个Ingredient数组,你也可以在序列化时做一些其它的方式来简化它的结构。
result*的两个选字段分别表示了合成结果及其结果的数量,即1个粘液球,这两个字段最终会一起成为一个ItemStack实例
这里采用了两种class来表示输入和输出,你也可以只采用一种,比如只采用ItemStack,此处我写出两种仅是因为MC自己的合成台代码里采用了这两种混合的方式,因为Ingredient有一个test方法来对方块进行对比,但我认为自己去编写这个对比方式或许会更好(尽管我并没有在这么去做)。
下面是对应的实体类和序列器:

/**
 * 合成表的抽象
 */
public class WorkBlockRecipe implements Recipe<WorkBlockInputInventory> {
    // 合成表本身的输入,也可以不是数组,主要是在json序列化时不出问题即可
    private final Ingredient[] inputStackList;
    // 合成表本身的输入所对应的输出
    private final ItemStack outputStack;

    private final Identifier id;

    public WorkBlockRecipe(Ingredient[] inputStackList, ItemStack outputStack, Identifier id) {
        this.inputStackList = inputStackList;
        this.outputStack = outputStack;
        // 网络读写需要,基本是一个合成表一个id
        this.id = id;
    }

    // 合成表也需要一个Type,是fabric的规定,重写好toString即可
    public static class Type implements RecipeType<WorkBlockRecipe> {
        private Type() {}
        public static final Type INSTANCE = new Type();
        public static final String NAME = "work_recipe_type";
        public static final Identifier ID = new Identifier(ModInfo.MOD_ID, NAME);

        @Override
        public String toString() {
            return NAME;
        }
    }

    public Ingredient[] getInputStackList() {
        return inputStackList;
    }

    public ItemStack getOutputStack() {
        return outputStack;
    }


    // fabric在创建世界时会将所有合成表存放到内存里,然后调用这个方法进行匹配
    @Override
    public boolean matches(WorkBlockInputInventory inventory, World world) {
        if(inventory.size() < 2) return false;
        for (int i = 0; i < 9; i++) {
            if(!inputStackList[i].test(inventory.getStack(i))){
                return false;
            }
        }
        return true;
    }

    // 进行合成
    @Override
    public ItemStack craft(WorkBlockInputInventory inventory, DynamicRegistryManager registryManager) {
        return this.getOutputStack().copy();
    }

    // 可能是校验合成表长宽是否合适吧,你可以在里面做一点判断
    @Override
    public boolean fits(int width, int height) {
        return true;
    }

    @Override
    public ItemStack getOutput(DynamicRegistryManager registryManager) {
        return outputStack;
    }

    @Override
    public Identifier getId() {
        return this.id;
    }

    // 序列化工具,用于网络传输
    @Override
    public RecipeSerializer<?> getSerializer() {
        return WorkBlockRecipeSerializer.INSTANCE;
    }

    @Override
    public RecipeType<?> getType() {
        return Type.INSTANCE;
    }
}
// 序列化工具,用于将服务端验证后的合成表的数据传输给客户端以及将json合成表转换成合成表对象实例
public class WorkBlockRecipeSerializer implements RecipeSerializer<WorkBlockRecipe> {

    public static WorkBlockRecipeSerializer INSTANCE = new WorkBlockRecipeSerializer();

    public static String NAME = "work_recipe";

    public static Identifier ID = new Identifier(ModInfo.MOD_ID, NAME);

    // 将json文件转化为WorkBlockRecipe实例,可以根据你自己的设计需要进行
    @Override
    public WorkBlockRecipe read(Identifier identifier, JsonObject jsonObject) {
        JsonArray input = jsonObject.get("input").getAsJsonArray();
        Ingredient[] ingredient = new Ingredient[9];
        for (int i = 0; i < 9; i++) {
            ingredient[i] = Ingredient.ofItems(Registries.ITEM.get(new Identifier("minecraft:air")));
        }
        for (int i = 0; i < input.size(); i++) {
            JsonElement jsonElement = input.get(i);
            Ingredient ingredientTmp = Ingredient.fromJson(jsonElement);
            ingredient[i] = ingredientTmp;
        }
        int resultCount = jsonObject.get("resultCount").getAsInt();
        String resultItem = jsonObject.get("resultItem").getAsString();
        if (resultItem == null) {
            throw new JsonSyntaxException("工作配方格式错误,resultItem异常");
        }
        Item item = Registries.ITEM.getOrEmpty(new Identifier(resultItem))
                .orElseThrow(() -> new JsonSyntaxException("无法找到对应resultItem产物"));
        ItemStack outputStack = new ItemStack(item, resultCount);
        return new WorkBlockRecipe(ingredient, outputStack, identifier);
    }

    // 由fabric调用,将得到的合成表序列化成二进制
    @Override
    public void write(PacketByteBuf buf, WorkBlockRecipe recipe) {
        for (int i = 0; i < 9; i++) {
            recipe.getInputStackList()[i].write(buf);
        }
        buf.writeItemStack(recipe.getOutputStack());
    }

    // 由fabric调用,将收到的二进制转为合成表
    @Override
    public WorkBlockRecipe read(Identifier id, PacketByteBuf buf) {
        Ingredient[] ingredient = new Ingredient[9];
        for (int i = 0; i < 9; i++) {
            ingredient[i] = Ingredient.fromPacket(buf);
        }
        ItemStack outputStack = buf.readItemStack();
        return new WorkBlockRecipe(ingredient, outputStack, id);
    }
}

至于合成表的目录结构建议阅读官方文档,我这里仅给出参考 src └─main ├─java │ └─x │ └─x │ └─amazing │ ├─block // 这里面放方块 │ │ └─entity // 这里面放方块实体 │ ├─client │ ├─constant // modinfo在这里面 │ ├─inventory // 输入输出接口在这里面 │ │ └─api // easyinventory在这里面 │ ├─item // 物品在这里 │ ├─recipe // 配方实体类 │ │ └─serializer // 配方序列化器 │ ├─screen // GUI │ │ ├─handler │ │ └─slot │ └─util └─resources ├─assets │ └─amazing │ ├─blockstates │ ├─lang │ ├─models │ │ ├─block │ │ └─item │ └─textures │ ├─block │ └─item └─data └─amazing ├─loot_tables │ └─blocks └─recipes // 合成表json文件在这里