我的世界Minecraft 1.19.2 Mod开发教程分享-22-自定义添加合成表和添加JEI
Flandre芙兰
编辑于 2023年07月09日 00:00
收录于文集
共36篇

更换mapping

更换了mapping减少变量的混淆。

这里使用的mapping地址。

安装方法和教程也在这里有。

https://parchmentmc.org/

在setting.gradle文件中添加parchmentmc的maven仓库。

添加这一行代码

代码块
JavaScript
自动换行
复制代码
```java
         maven { url = 'https://maven.parchmentmc.org' } // Add this line
```
复制成功

2. 添加librarian plugin到build.gradle文件,

3. 更新你的mappings为parchment的cahnnel。

修改其中的版本

代码块
JavaScript
自动换行
复制代码
```java
    mappings channel: 'parchment', version: '2022.11.27-1.19.2'
```
复制成功

4. 点击relaod gradle

等待build success

cut-off

## 添加JEI

在JEI的github仓库中提供了development的wiki

点击其中个get started jei[jei 10 or higher for forge or fabirc]

https://github.com/mezz/JustEnoughItems/wiki/Getting-Started-%5BJEI-10-or-higher-for-Forge-or-Fabric%5D

代码块
JavaScript
自动换行
复制代码
        maven {
            // location of the maven that hosts JEI files before January 2023
            name = "Progwml6's maven"
            url = "https://dvs1.progwml6.com/files/maven/"
        }
        maven {
            // location of the maven that hosts JEI files since January 2023
            name = "Jared's maven"
            url = "https://maven.blamejared.com/"
        }
        maven {
            // location of a maven mirror for JEI files, as a fallback
            name = "ModMaven"
            url = "https://modmaven.dev"
        }
复制成功

接下来在build.gradle中添加内容

代码块
JavaScript
自动换行
复制代码
dependencies {
  /* other minecraft dependencies are here */

  // compile against the JEI API but do not include it at runtime
  compileOnly(fg.deobf("mezz.jei:jei-${mc_version}-common-api:${jei_version}"))
  compileOnly(fg.deobf("mezz.jei:jei-${mc_version}-forge-api:${jei_version}"))
  // at runtime, use the full JEI jar for Forge
  runtimeOnly(fg.deobf("mezz.jei:jei-${mc_version}-forge:${jei_version}"))
}
复制成功

选择JEI的版本,通过下面的方式找到一个可用的版本,记住他。

在这个文件中添加,复制的内容。

将版本修改为你刚刚记得内容。

点击reload

等待build success

cut-off

自定义合成表

在我们更换了mapping之后,发现许多函数的参数的变量名就不是之前的混淆的名称了。

创建包和类

GemInfusingStationRecipe

ModRecipes

GemInfusingStationRecipe中放入如下的代码

代码块
JavaScript
自动换行
复制代码

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import net.flandre923.tutorialmod.TutorialMod;
import net.minecraft.core.NonNullList;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.SimpleContainer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.*;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;

// recipe类仅描述了配方数据和执行逻辑,
// 通过container子类提供数据
// 任何输入的Container都应该是不可变的,任何的操作都应该通过copy输入副本。

public class GemInfusingStationRecipe implements Recipe<SimpleContainer> {
    private final ResourceLocation id;
    private final ItemStack output;
    private final NonNullList<Ingredient> recipeItems;
    public GemInfusingStationRecipe(ResourceLocation id, ItemStack output,
                                    NonNullList<Ingredient> recipeItems){
        this.id = id;
        this.output = output;
        this.recipeItems = recipeItems;
    }
    // 为了能够通过管理器获得配方,match必须返回true
    // 此方法用于管理容器是否输入有效。
    // 通过代用test检测
    // 检查容器内的物品和配方是否匹配。
    @Override
    public boolean matches(SimpleContainer pContainer, Level pLevel) {
        if(pLevel.isClientSide()){
            return false;
        }
        // 如果recipeItems的第0个和container中的第一个匹配那么返回true
        return recipeItems.get(0).test(pContainer.getItem(1));
    }
    // 获得合成表所需要的item stacks
    @Override
    public NonNullList<Ingredient> getIngredients() {
        return recipeItems;
    }

    // 构建配方
    // 返回了合成表的结果output
    @Override
    public ItemStack assemble(SimpleContainer pContainer) {
        return output;
    }
    // 这个方法用于判断合成表是否可以在指定的dimensions合成。
    @Override
    public boolean canCraftInDimensions(int pWidth, int pHeight) {
        return true;
    }
    // 获得合成表物品的copy()
    @Override
    public ItemStack getResultItem() {
        return output.copy();
    }

    //
    @Override
    public ResourceLocation getId() {
        return id;
    }
    // 返回Serializer 必须返回
    @Override
    public RecipeSerializer<?> getSerializer() {
        return Serializer.INSTANCE;
    }
    // 返回type
    @Override
    public RecipeType<?> getType() {
        return Type.INSTANCE;
    }

    // 注册新的合成的type
    public static class Type implements RecipeType<GemInfusingStationRecipe>{
        private Type(){}
        public static final Type INSTANCE = new Type();
        // 标识了合成的类型,和json文件中的type一致
        public static final String ID = "gem_infusing";
    }

    // 负责解码JSON并通过网络通信
    // 需要注册
    public static class Serializer implements RecipeSerializer<GemInfusingStationRecipe> {
        public static final Serializer INSTANCE = new Serializer();
        public static final  ResourceLocation ID =
                new ResourceLocation(TutorialMod.MOD_ID,"gem_infusing");
        // 将JSON解码为recipe子类型
        @Override
        public GemInfusingStationRecipe fromJson(ResourceLocation pRecipeId, JsonObject pSerializedRecipe) {
            ItemStack output = ShapedRecipe.itemStackFromJson(GsonHelper.getAsJsonObject(pSerializedRecipe,"output"));

            JsonArray ingredients = GsonHelper.getAsJsonArray(pSerializedRecipe,"ingredients");
            NonNullList<Ingredient> inputs = NonNullList.withSize(1,Ingredient.EMPTY);

            for(int i =0;i<inputs.size();i++){
                inputs.set(i,Ingredient.fromJson(ingredients.get(i)));
            }
            return new GemInfusingStationRecipe(pRecipeId,output,inputs);
        }
        // 从服务器中发送的数据中解码recipe,配方标识符不需要解码。
        @Override
        public @Nullable GemInfusingStationRecipe fromNetwork(ResourceLocation pRecipeId, FriendlyByteBuf pBuffer) {
            NonNullList<Ingredient> inputs = NonNullList.withSize(pBuffer.readInt(),Ingredient.EMPTY);
            for (int i=0;i < inputs.size();i++){
                inputs.set(i,Ingredient.fromNetwork(pBuffer));
            }
            ItemStack output = pBuffer.readItem();
            return new GemInfusingStationRecipe(pRecipeId,output,inputs);
        }

        @Override
        public void toNetwork(FriendlyByteBuf pBuffer, GemInfusingStationRecipe pRecipe) {
            pBuffer.writeInt(pRecipe.getIngredients().size());
            for (Ingredient ing : pRecipe.getIngredients()){
                ing.toNetwork(pBuffer);
            }
            pBuffer.writeItemStack(pRecipe.getResultItem(),false);
        }
    }


}
复制成功

代码块
JavaScript
自动换行
复制代码


import net.kaupenjoe.tutorialmod.TutorialMod;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;

public class ModRecipes {
    public static final DeferredRegister<RecipeSerializer<?>> SERIALIZERS =
            DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, TutorialMod.MOD_ID);

    public static final RegistryObject<RecipeSerializer<GemInfusingStationRecipe>> GEM_INFUSING_SERIALIZER =
            SERIALIZERS.register("gem_infusing", () -> GemInfusingStationRecipe.Serializer.INSTANCE);

    public static void register(IEventBus eventBus) {
        SERIALIZERS.register(eventBus);
    }
}
复制成功

记得注册当前的RecipeSerializer到总线上

代码块
JavaScript
自动换行
复制代码
        ModRecipes.register(modEventBus);
复制成功

添加自己的合成表

其中type是我们加入的类型,ingredients是所需要的内容。和我们写的RecipeSerializer的字段一致。

output也和我们写的RecipeSerializer中的字段一致。

zircon_from_infusing.json

代码块
JavaScript
自动换行
复制代码
{
  "type": "tutorialmod:gem_infusing",
  "ingredients": [
    {
      "item": "tutorialmod:raw_zircon"
    }
  ],
  "output": {
    "item": "tutorialmod:zircon"
  }
}
复制成功

再添加一个合成表。使用木棍合成钻石

/data/tutorialmod/recipes/diamond_from_infusing.json

代码块
JavaScript
自动换行
复制代码
{
  "type": "tutorialmod:gem_infusing",
  "ingredients": [
    {
      "item": "minecraft:stick"
    }
  ],
  "output": {
    "item": "minecraft:diamond"
  }
}
复制成功

接下来修改我们的之前的entity其中与合成相关的方法。

找到这个方法

修改这个方法。

代码块
JavaScript
自动换行
复制代码

    private static void craftItem(GemInfusingStationBlockEntity entity) {
        Level level = entity.level;
        SimpleContainer inventory = new SimpleContainer(entity.itemStackHandler.getSlots());
        for(int i=0;i < entity.itemStackHandler.getSlots() ; i++){
            inventory.setItem(i,entity.itemStackHandler.getStackInSlot(i));
        }
        // 获得当前的recipe
        Optional<GemInfusingStationRecipe> recipe = level.getRecipeManager().getRecipeFor(GemInfusingStationRecipe.Type.INSTANCE,inventory,level);

        if(hasRecipe(entity)){
            // 合成的结果是recipe的result
            entity.itemStackHandler.extractItem(1,1,false);
            entity.itemStackHandler.setStackInSlot(2,new ItemStack(recipe.get().getResultItem().getItem(),
                    entity.itemStackHandler.getStackInSlot(2).getCount() + 1));
            entity.resetProgress();
        }
    }
复制成功

修改这个方法

代码块
JavaScript
自动换行
复制代码

    private static boolean hasRecipe(GemInfusingStationBlockEntity entity) {
        Level level = entity.level;
        SimpleContainer inventory = new SimpleContainer(entity.itemStackHandler.getSlots());
        for(int i=0;i<entity.itemStackHandler.getSlots();i++){
            inventory.setItem(i,entity.itemStackHandler.getStackInSlot(i));
        }
        Optional<GemInfusingStationRecipe> recipe = level.getRecipeManager().getRecipeFor(GemInfusingStationRecipe.Type
                .INSTANCE,inventory,level);

        return recipe.isPresent() && canInsertAmountInToOutputSlot(inventory)&&
                canInsertItemToOutputSlot(inventory,recipe.get().getResultItem());
    }
复制成功

cut-off

测试

可以看到这里已经有了JEI了

可以看到我们加入的物品

可以看到我们的第一个合成表在正常的工作。

第二个合成表

也是正常工作了。

cut-off

加入JEI合成表

现在我们还没办法在JEI中查看我们的合成表

加入如下的包和类

在GemInfusingStationRecipeCategory中加入如下的代码

代码块
JavaScript
自动换行
复制代码

import mezz.jei.api.constants.VanillaTypes;
import mezz.jei.api.gui.builder.IRecipeLayoutBuilder;
import mezz.jei.api.gui.drawable.IDrawable;
import mezz.jei.api.helpers.IGuiHelper;
import mezz.jei.api.recipe.IFocusGroup;
import mezz.jei.api.recipe.RecipeIngredientRole;
import mezz.jei.api.recipe.RecipeType;
import mezz.jei.api.recipe.category.IRecipeCategory;
import net.flandre923.tutorialmod.TutorialMod;
import net.flandre923.tutorialmod.block.ModBlocks;
import net.flandre923.tutorialmod.recipe.GemInfusingStationRecipe;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;

// 自定义一个JEI的合成分类
public class GemInfusingStationRecipeCategory implements IRecipeCategory<GemInfusingStationRecipe> {
    // 区分合成分类的ID
    public static final ResourceLocation UID = new ResourceLocation(TutorialMod.MOD_ID,
            "gem_infusing");
    // png file texture
    public static final ResourceLocation TEXTURE = new ResourceLocation(TutorialMod.MOD_ID,
            "textures/gui/gem_infusing_station_gui.png");

    // 合成分类的背景图片
    private final IDrawable background;
    // 合成分类的图标
    private final IDrawable icon;

    // 构造方法
    public GemInfusingStationRecipeCategory(IGuiHelper helper){
        // 渲染背景图片。图片的开始位置和图片的结束的位置 u,v,width,height
        this.background  = helper.createDrawable(TEXTURE,0,0,176,85);
        // 图标
        this.icon = helper.createDrawableIngredient(VanillaTypes.ITEM_STACK,new ItemStack(ModBlocks.GEM_INFUSING_STATION.get()));

    }

    // 返回JEITutorialModPlugin定义的type
    @Override
    public RecipeType<GemInfusingStationRecipe> getRecipeType() {
        return JEITutorialModPlugin.INFUSION_TYPE;
    }

    // 合成界面的标题是什么
    @Override
    public Component getTitle() {
        return Component.literal("Gem Infusing Station");
    }
    //
    @Override
    public IDrawable getBackground() {
        return this.background;
    }

    @Override
    public IDrawable getIcon() {
        return this.icon;
    }

    // 添加合成表的输入slot和输出的slot
    @Override
    public void setRecipe(IRecipeLayoutBuilder builder, GemInfusingStationRecipe recipe, IFocusGroup focuses) {
        // 在86,15的位置设置一个用于输入的slot,内容为recipe的ingredients的第0个。
        builder.addSlot(RecipeIngredientRole.INPUT,86,15).addIngredients(recipe.getIngredients().get(0));
        // 在86,60的位置设置一个用于输出的slot,内容为recipe的合成结果。
        builder.addSlot(RecipeIngredientRole.OUTPUT,86,60).addItemStack(recipe.getResultItem());
    }
}
复制成功

JEITutorialPlugin类中加入如下的内容:

代码块
JavaScript
自动换行
复制代码

import mezz.jei.api.IModPlugin;
import mezz.jei.api.JeiPlugin;
import mezz.jei.api.recipe.RecipeType;
import mezz.jei.api.registration.IRecipeCategoryRegistration;
import mezz.jei.api.registration.IRecipeRegistration;
import net.flandre923.tutorialmod.TutorialMod;
import net.flandre923.tutorialmod.recipe.GemInfusingStationRecipe;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.crafting.RecipeManager;

import java.util.List;
import java.util.Objects;

@JeiPlugin
public class JEITutorialModPlugin implements IModPlugin {
    // 使用category指定一个type
    public static RecipeType<GemInfusingStationRecipe> INFUSION_TYPE =
            new RecipeType<>(GemInfusingStationRecipeCategory.UID, GemInfusingStationRecipe.class);


    //返回插件的id
    @Override
    public ResourceLocation getPluginUid() {
        return new ResourceLocation(TutorialMod.MOD_ID,"jei_plugin");

    }
    // 将type注册到JEIplugin
    // 自定义物品合成的分类以及该分类下的物品的位置
    @Override
    public void registerCategories(IRecipeCategoryRegistration registration) {
        registration.addRecipeCategories(new GemInfusingStationRecipeCategory(registration.getJeiHelpers().getGuiHelper()));
    }

    // 注册合成的信息
    // 获得所有的该type下的所有合成表。
    // 注册type的合成表
    @Override
    public void registerRecipes(IRecipeRegistration registration) {
        RecipeManager rm = Objects.requireNonNull(Minecraft.getInstance().level).getRecipeManager();
        List<GemInfusingStationRecipe> recipesInfusing = rm.getAllRecipesFor(GemInfusingStationRecipe.Type.INSTANCE);

        registration.addRecipes(INFUSION_TYPE,recipesInfusing);
    }
}
复制成功

测试

可以看到加入完成了