更换mapping
更换了mapping减少变量的混淆。
这里使用的mapping地址。
安装方法和教程也在这里有。
https://parchmentmc.org/
在setting.gradle文件中添加parchmentmc的maven仓库。


添加这一行代码

```java
maven { url = 'https://maven.parchmentmc.org' } // Add this line
``` 2. 添加librarian plugin到build.gradle文件,


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

修改其中的版本


```java
mappings channel: 'parchment', version: '2022.11.27-1.19.2'
``` 4. 点击relaod gradle

等待build success


## 添加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

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中添加内容


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


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

创建包和类

GemInfusingStationRecipe
ModRecipes
GemInfusingStationRecipe中放入如下的代码
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);
}
}
}
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到总线上
ModRecipes.register(modEventBus); 添加自己的合成表

其中type是我们加入的类型,ingredients是所需要的内容。和我们写的RecipeSerializer的字段一致。
output也和我们写的RecipeSerializer中的字段一致。
zircon_from_infusing.json
{
"type": "tutorialmod:gem_infusing",
"ingredients": [
{
"item": "tutorialmod:raw_zircon"
}
],
"output": {
"item": "tutorialmod:zircon"
}
} 再添加一个合成表。使用木棍合成钻石
/data/tutorialmod/recipes/diamond_from_infusing.json
{
"type": "tutorialmod:gem_infusing",
"ingredients": [
{
"item": "minecraft:stick"
}
],
"output": {
"item": "minecraft:diamond"
}
} 接下来修改我们的之前的entity其中与合成相关的方法。

找到这个方法

修改这个方法。
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();
}
} 修改这个方法

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());
}

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

可以看到我们加入的物品

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

第二个合成表
也是正常工作了。


加入JEI合成表
现在我们还没办法在JEI中查看我们的合成表
加入如下的包和类

在GemInfusingStationRecipeCategory中加入如下的代码
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类中加入如下的内容:
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);
}
} 测试
可以看到加入完成了

