提交
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
package thaumicenergistics.integration.jei;
|
||||
|
||||
import tamaized.ae2jeiintegration.api.integrations.jei.IngredientConverters;
|
||||
|
||||
/**
|
||||
* AE2-JEI-Integration 兼容注册的隔离类。
|
||||
* 仅被 {@link ThEJeiPlugin} 通过反射调用({@code Class.forName} + {@code getMethod("register").invoke}),
|
||||
* 本类(及其引用的 ae2jei 库类)只在库存在时才被类加载器解析。
|
||||
* 因此只装 JEI 时不会因引用缺失而 NoClassDefFoundError,双装时拖拽转换完整生效。
|
||||
*/
|
||||
public final class Ae2JeiCompat {
|
||||
|
||||
private Ae2JeiCompat() {
|
||||
}
|
||||
|
||||
/** 注册源质转换器到 AE2-JEI-Integration(支持从 JEI 拖拽源质到 AE2 筛选槽)。 */
|
||||
public static void register() {
|
||||
IngredientConverters.register(new EssentiaIngredientConverter());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package thaumicenergistics.integration.jei;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.crafting.CraftingRecipe;
|
||||
import net.minecraft.world.item.crafting.RecipeHolder;
|
||||
|
||||
import mezz.jei.api.constants.RecipeTypes;
|
||||
import mezz.jei.api.recipe.transfer.IRecipeTransferInfo;
|
||||
|
||||
import appeng.menu.SlotSemantics;
|
||||
import thaumicenergistics.common.container.ContainerArcaneCraftingTerminal;
|
||||
import thaumicenergistics.init.ModMenuTypes;
|
||||
|
||||
/**
|
||||
* 为奥术合成终端提供 JEI 配方转移的槽位映射。
|
||||
* 让 JEI 知道哪些槽是合成格(CRAFTING_GRID)、哪些是玩家背包。
|
||||
* 实际转移走网络由 ThEUseCraftingRecipeTransfer 处理。
|
||||
*/
|
||||
public class ArcaneCraftingTransferInfo
|
||||
implements IRecipeTransferInfo<ContainerArcaneCraftingTerminal, RecipeHolder<CraftingRecipe>> {
|
||||
|
||||
@Override
|
||||
public Class<? extends ContainerArcaneCraftingTerminal> getContainerClass() {
|
||||
return ContainerArcaneCraftingTerminal.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MenuType<ContainerArcaneCraftingTerminal>> getMenuType() {
|
||||
return Optional.of(ModMenuTypes.ARCANE_CRAFTING_TERMINAL.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public mezz.jei.api.recipe.RecipeType<RecipeHolder<CraftingRecipe>> getRecipeType() {
|
||||
return RecipeTypes.CRAFTING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHandle(ContainerArcaneCraftingTerminal menu, RecipeHolder<CraftingRecipe> recipe) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Slot> getRecipeSlots(ContainerArcaneCraftingTerminal menu, RecipeHolder<CraftingRecipe> recipe) {
|
||||
return menu.getSlots(appeng.menu.SlotSemantics.CRAFTING_GRID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Slot> getInventorySlots(ContainerArcaneCraftingTerminal menu, RecipeHolder<CraftingRecipe> recipe) {
|
||||
return menu.getSlots(appeng.menu.SlotSemantics.PLAYER_INVENTORY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package thaumicenergistics.integration.jei;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
|
||||
import mezz.jei.api.recipe.RecipeType;
|
||||
import mezz.jei.api.recipe.transfer.IRecipeTransferInfo;
|
||||
import appeng.menu.SlotSemantics;
|
||||
import thaumcraft.integration.jei.JeiArcaneRecipe;
|
||||
import thaumcraft.integration.jei.ThaumcraftJeiPlugin;
|
||||
import thaumicenergistics.common.container.ContainerArcaneCraftingTerminal;
|
||||
import thaumicenergistics.init.ModMenuTypes;
|
||||
|
||||
/**
|
||||
* JEI 配方转移信息:支持 TC4 奥术配方(JeiArcaneRecipe)。
|
||||
* 与 ArcaneCraftingTransferInfo(vanilla 合成)配合使用。
|
||||
*/
|
||||
public class ArcaneRecipeTransferInfo implements IRecipeTransferInfo<ContainerArcaneCraftingTerminal, JeiArcaneRecipe> {
|
||||
|
||||
@Override
|
||||
public Class<? extends ContainerArcaneCraftingTerminal> getContainerClass() {
|
||||
return ContainerArcaneCraftingTerminal.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MenuType<ContainerArcaneCraftingTerminal>> getMenuType() {
|
||||
return Optional.of(ModMenuTypes.ARCANE_CRAFTING_TERMINAL.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeType<JeiArcaneRecipe> getRecipeType() {
|
||||
return ThaumcraftJeiPlugin.ARCANE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHandle(ContainerArcaneCraftingTerminal menu, JeiArcaneRecipe recipe) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Slot> getRecipeSlots(ContainerArcaneCraftingTerminal menu, JeiArcaneRecipe recipe) {
|
||||
return menu.getSlots(SlotSemantics.CRAFTING_GRID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Slot> getInventorySlots(ContainerArcaneCraftingTerminal menu, JeiArcaneRecipe recipe) {
|
||||
List<Slot> inventorySlots = new ArrayList<>();
|
||||
var playerInv = menu.getPlayerInventory();
|
||||
for (Slot slot : menu.slots) {
|
||||
if (slot != null && slot.container == playerInv) {
|
||||
inventorySlots.add(slot);
|
||||
}
|
||||
}
|
||||
return inventorySlots;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package thaumicenergistics.integration.jei;
|
||||
|
||||
import thaumcraft.api.aspects.Aspect;
|
||||
|
||||
/**
|
||||
* JEI 自定义成分:持有 Thaumcraft Aspect 实例。
|
||||
* 不创建虚拟物品,不污染物品注册表。
|
||||
* JEI 侧边栏直接显示要素图标,支持搜索和拖拽。
|
||||
*/
|
||||
public record AspectIngredient(Aspect aspect) {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AspectIngredient{" + aspect.getTag() + "}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package thaumicenergistics.integration.jei;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import mezz.jei.api.ingredients.IIngredientHelper;
|
||||
import mezz.jei.api.ingredients.subtypes.UidContext;
|
||||
import thaumcraft.api.aspects.Aspect;
|
||||
import thaumicenergistics.common.integration.tc.TCReflection;
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
public class AspectIngredientHelper implements IIngredientHelper<AspectIngredient> {
|
||||
|
||||
public static final AspectIngredientHelper INSTANCE = new AspectIngredientHelper();
|
||||
|
||||
private AspectIngredientHelper() {}
|
||||
|
||||
@Override
|
||||
public AspectIngredientType getIngredientType() {
|
||||
return AspectIngredientType.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName(AspectIngredient ingredient) {
|
||||
if (ingredient == null || ingredient.aspect() == null) return "Unknown";
|
||||
return ingredient.aspect().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getUid(AspectIngredient ingredient, UidContext context) {
|
||||
if (ingredient == null || ingredient.aspect() == null) return "thaumcraft:unknown";
|
||||
ResourceLocation id = TCReflection.getAspectId(ingredient.aspect());
|
||||
return id != null ? id.toString() : "thaumcraft:" + ingredient.aspect().getTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUniqueId(AspectIngredient ingredient, UidContext context) {
|
||||
return getUid(ingredient, context).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getResourceLocation(AspectIngredient ingredient) {
|
||||
if (ingredient == null || ingredient.aspect() == null) return ResourceLocation.withDefaultNamespace("air");
|
||||
ResourceLocation id = TCReflection.getAspectId(ingredient.aspect());
|
||||
return id != null ? id : ResourceLocation.withDefaultNamespace(ingredient.aspect().getTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AspectIngredient copyIngredient(AspectIngredient ingredient) {
|
||||
return ingredient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidIngredient(AspectIngredient ingredient) {
|
||||
return ingredient != null && ingredient.aspect() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIngredientOnServer(AspectIngredient ingredient) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorInfo(AspectIngredient ingredient) {
|
||||
if (ingredient == null) return "Null AspectIngredient";
|
||||
if (ingredient.aspect() == null) return "Null Aspect";
|
||||
return "Aspect: " + ingredient.aspect().getTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayModId(AspectIngredient ingredient) {
|
||||
return "thaumcraft";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package thaumicenergistics.integration.jei;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
|
||||
import mezz.jei.api.ingredients.IIngredientRenderer;
|
||||
import thaumcraft.api.aspects.Aspect;
|
||||
import thaumcraft.client.gui.AspectGuiRenderer;
|
||||
|
||||
public class AspectIngredientRenderer implements IIngredientRenderer<AspectIngredient> {
|
||||
|
||||
public static final AspectIngredientRenderer INSTANCE = new AspectIngredientRenderer();
|
||||
|
||||
private static final int SIZE = 16;
|
||||
|
||||
private AspectIngredientRenderer() {}
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics guiGraphics, AspectIngredient ingredient) {
|
||||
if (ingredient != null && ingredient.aspect() != null) {
|
||||
AspectGuiRenderer.draw(guiGraphics, ingredient.aspect(), 0, 0, SIZE, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Component> getTooltip(AspectIngredient ingredient, TooltipFlag tooltipFlag) {
|
||||
List<Component> tooltip = new ArrayList<>();
|
||||
if (ingredient == null || ingredient.aspect() == null) {
|
||||
return tooltip;
|
||||
}
|
||||
Aspect aspect = ingredient.aspect();
|
||||
tooltip.add(Component.literal(aspect.getName()));
|
||||
tooltip.add(Component.literal(aspect.getTag()).withStyle(style -> style.withColor(0x888888)));
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return SIZE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package thaumicenergistics.integration.jei;
|
||||
|
||||
import mezz.jei.api.ingredients.IIngredientType;
|
||||
|
||||
/**
|
||||
* JEI 自定义成分类型:AspectIngredient。
|
||||
*/
|
||||
public class AspectIngredientType implements IIngredientType<AspectIngredient> {
|
||||
|
||||
public static final AspectIngredientType INSTANCE = new AspectIngredientType();
|
||||
|
||||
private AspectIngredientType() {}
|
||||
|
||||
@Override
|
||||
public Class<? extends AspectIngredient> getIngredientClass() {
|
||||
return AspectIngredient.class;
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package thaumicenergistics.integration.jei;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.client.renderer.Rect2i;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import mezz.jei.api.gui.handlers.IGhostIngredientHandler;
|
||||
import mezz.jei.api.ingredients.ITypedIngredient;
|
||||
|
||||
import thaumicenergistics.common.container.GuiEssentiaCellWorkbench;
|
||||
import thaumicenergistics.common.integration.tc.TCReflection;
|
||||
import thaumicenergistics.common.network.CellWorkbenchC2SPacket;
|
||||
|
||||
/**
|
||||
* 源质元件工作台分区格的 JEI 拖拽支持:从 JEI 拖拽要素到 63 个分区格之一 → 添加该要素分区。
|
||||
* 纯 JEI API 实现,不依赖 AE2-JEI-Integration(只装 JEI 即可用)。
|
||||
*/
|
||||
public class CellWorkbenchGhostIngredientHandler implements IGhostIngredientHandler<GuiEssentiaCellWorkbench> {
|
||||
|
||||
@Override
|
||||
public <I> List<Target<I>> getTargetsTyped(GuiEssentiaCellWorkbench gui, ITypedIngredient<I> ingredient, boolean doStart) {
|
||||
List<Target<I>> targets = new ArrayList<>();
|
||||
if (!(ingredient.getIngredient() instanceof AspectIngredient)) {
|
||||
return targets;
|
||||
}
|
||||
// 63 个分区格都是拖放目标
|
||||
for (int i = 0; i < GuiEssentiaCellWorkbench.NUMBER_OF_WIDGETS; i++) {
|
||||
int col = i % GuiEssentiaCellWorkbench.WIDGETS_PER_ROW;
|
||||
int row = i / GuiEssentiaCellWorkbench.WIDGETS_PER_ROW;
|
||||
int wx = gui.getGuiLeft() + GuiEssentiaCellWorkbench.WIDGET_POS_X + col * GuiEssentiaCellWorkbench.WIDGET_SIZE;
|
||||
int wy = gui.getGuiTop() + GuiEssentiaCellWorkbench.WIDGET_POS_Y + row * GuiEssentiaCellWorkbench.WIDGET_SIZE;
|
||||
targets.add(new Target<>() {
|
||||
@Override
|
||||
public Rect2i getArea() {
|
||||
return new Rect2i(wx, wy, GuiEssentiaCellWorkbench.WIDGET_SIZE, GuiEssentiaCellWorkbench.WIDGET_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(I ingredient) {
|
||||
if (ingredient instanceof AspectIngredient ai && ai.aspect() != null) {
|
||||
ResourceLocation aspectId = TCReflection.getAspectId(ai.aspect());
|
||||
if (aspectId != null) {
|
||||
CellWorkbenchC2SPacket.sendAddAspect(aspectId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package thaumicenergistics.integration.jei;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import mezz.jei.api.ingredients.IIngredientType;
|
||||
|
||||
import appeng.api.stacks.GenericStack;
|
||||
import appeng.api.stacks.AEKey;
|
||||
import tamaized.ae2jeiintegration.api.integrations.jei.IngredientConverter;
|
||||
import thaumcraft.api.aspects.Aspect;
|
||||
import thaumicenergistics.common.integration.appeng.AEssentiaKey;
|
||||
import thaumicenergistics.common.integration.tc.TCReflection;
|
||||
|
||||
/**
|
||||
* 源质转换器:GenericStack(AEssentiaKey) ↔ AspectIngredient。
|
||||
* 通过 ServiceLoader 自动注册到 AE2-JEI-Integration。
|
||||
* 实现从 JEI 拖拽源质到 AE2 筛选槽的功能。
|
||||
*/
|
||||
public class EssentiaIngredientConverter implements IngredientConverter<AspectIngredient> {
|
||||
|
||||
@Override
|
||||
public IIngredientType<AspectIngredient> getIngredientType() {
|
||||
return AspectIngredientType.INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable AspectIngredient getIngredientFromStack(GenericStack stack) {
|
||||
if (stack == null) return null;
|
||||
AEKey what = stack.what();
|
||||
if (what instanceof AEssentiaKey essentiaKey) {
|
||||
ResourceLocation id = essentiaKey.getId();
|
||||
Aspect aspect = Aspect.getAspect(id.getPath());
|
||||
if (aspect != null) {
|
||||
return new AspectIngredient(aspect);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable GenericStack getStackFromIngredient(AspectIngredient ingredient) {
|
||||
if (ingredient == null || ingredient.aspect() == null) return null;
|
||||
var aspectId = TCReflection.getAspectId(ingredient.aspect());
|
||||
if (aspectId == null) return null;
|
||||
return new GenericStack(AEssentiaKey.of(aspectId), 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package thaumicenergistics.integration.jei;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraft.world.inventory.Slot;
|
||||
import net.minecraft.world.item.crafting.CraftingRecipe;
|
||||
import net.minecraft.world.item.crafting.RecipeHolder;
|
||||
|
||||
import mezz.jei.api.constants.RecipeTypes;
|
||||
import mezz.jei.api.recipe.RecipeType;
|
||||
import mezz.jei.api.recipe.transfer.IRecipeTransferInfo;
|
||||
import appeng.menu.SlotSemantics;
|
||||
import thaumicenergistics.common.container.ContainerArcaneCraftingTerminal;
|
||||
import thaumicenergistics.init.ModMenuTypes;
|
||||
|
||||
/**
|
||||
* JEI 配方转移信息:告诉 JEI 哪些槽位是合成网格,哪些是玩家背包。
|
||||
* 这样 JEI 的 "+" 按钮就能将配方转移到奥术合成台。
|
||||
*/
|
||||
public class ThEArcaneCraftingTransferInfo implements IRecipeTransferInfo<ContainerArcaneCraftingTerminal, RecipeHolder<CraftingRecipe>> {
|
||||
|
||||
@Override
|
||||
public Class<? extends ContainerArcaneCraftingTerminal> getContainerClass() {
|
||||
return ContainerArcaneCraftingTerminal.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MenuType<ContainerArcaneCraftingTerminal>> getMenuType() {
|
||||
return Optional.of(ModMenuTypes.ARCANE_CRAFTING_TERMINAL.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecipeType<RecipeHolder<CraftingRecipe>> getRecipeType() {
|
||||
return RecipeTypes.CRAFTING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHandle(ContainerArcaneCraftingTerminal container, RecipeHolder<CraftingRecipe> recipe) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Slot> getRecipeSlots(ContainerArcaneCraftingTerminal container, RecipeHolder<CraftingRecipe> recipe) {
|
||||
return container.getSlots(SlotSemantics.CRAFTING_GRID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Slot> getInventorySlots(ContainerArcaneCraftingTerminal container, RecipeHolder<CraftingRecipe> recipe) {
|
||||
return container.getSlots(SlotSemantics.PLAYER_INVENTORY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package thaumicenergistics.integration.jei;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.renderer.Rect2i;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import mezz.jei.api.gui.handlers.IGhostIngredientHandler;
|
||||
import mezz.jei.api.ingredients.ITypedIngredient;
|
||||
|
||||
import appeng.api.stacks.GenericStack;
|
||||
import appeng.menu.AEBaseMenu;
|
||||
import appeng.menu.slot.FakeSlot;
|
||||
|
||||
/**
|
||||
* 处理从 JEI 拖拽到 AE2 Screen 的筛选槽。
|
||||
* 支持两种拖拽方式:
|
||||
* 1. 拖拽 WrappedGenericStack ItemStack(AE2 原生支持)
|
||||
* 2. 拖拽 AspectIngredient(自定义成分类型)
|
||||
*/
|
||||
public class ThEGhostIngredientHandler implements IGhostIngredientHandler<Screen> {
|
||||
|
||||
@Override
|
||||
public <I> List<Target<I>> getTargetsTyped(Screen screen, ITypedIngredient<I> ingredient, boolean doStart) {
|
||||
List<Target<I>> targets = new ArrayList<>();
|
||||
|
||||
if (!(screen instanceof appeng.client.gui.AEBaseScreen<?> aeScreen)) {
|
||||
return targets;
|
||||
}
|
||||
|
||||
if (!(aeScreen.getMenu() instanceof AEBaseMenu menu)) {
|
||||
return targets;
|
||||
}
|
||||
|
||||
if (!canHandleIngredient(ingredient)) {
|
||||
return targets;
|
||||
}
|
||||
|
||||
for (var slot : menu.slots) {
|
||||
if (slot instanceof FakeSlot fakeSlot) {
|
||||
targets.add(new Target<>() {
|
||||
@Override
|
||||
public Rect2i getArea() {
|
||||
return new Rect2i(
|
||||
aeScreen.getGuiLeft() + fakeSlot.x,
|
||||
aeScreen.getGuiTop() + fakeSlot.y,
|
||||
16, 16
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(I ingredient) {
|
||||
handleIngredientDrop(ingredient, fakeSlot);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
private <I> boolean canHandleIngredient(ITypedIngredient<I> ingredient) {
|
||||
if (ingredient.getIngredient() instanceof AspectIngredient) {
|
||||
return true;
|
||||
}
|
||||
if (ingredient.getIngredient() instanceof ItemStack stack) {
|
||||
return GenericStack.unwrapItemStack(stack) != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private <I> void handleIngredientDrop(I ingredient, FakeSlot fakeSlot) {
|
||||
ItemStack targetStack = ItemStack.EMPTY;
|
||||
|
||||
if (ingredient instanceof AspectIngredient ai && ai.aspect() != null) {
|
||||
// AspectIngredient → WrappedGenericStack ItemStack
|
||||
var aspectId = thaumicenergistics.common.integration.tc.TCReflection.getAspectId(ai.aspect());
|
||||
if (aspectId != null) {
|
||||
var key = thaumicenergistics.common.integration.appeng.AEssentiaKey.of(aspectId);
|
||||
targetStack = GenericStack.wrapInItemStack(key, 0);
|
||||
}
|
||||
} else if (ingredient instanceof ItemStack stack) {
|
||||
// WrappedGenericStack ItemStack(AE2 原生支持,直接放入)
|
||||
if (GenericStack.unwrapItemStack(stack) != null) {
|
||||
targetStack = stack;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetStack.isEmpty()) {
|
||||
fakeSlot.set(targetStack);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package thaumicenergistics.integration.jei;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DataResult;
|
||||
|
||||
import mezz.jei.api.IModPlugin;
|
||||
import mezz.jei.api.JeiPlugin;
|
||||
import mezz.jei.api.registration.IGuiHandlerRegistration;
|
||||
import mezz.jei.api.registration.IModIngredientRegistration;
|
||||
import mezz.jei.api.registration.IRecipeTransferRegistration;
|
||||
|
||||
import static mezz.jei.api.constants.RecipeTypes.CRAFTING;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import thaumcraft.api.aspects.Aspect;
|
||||
import thaumcraft.integration.jei.ThaumcraftJeiPlugin;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import thaumicenergistics.common.container.GuiEssentiaCellWorkbench;
|
||||
import thaumicenergistics.common.integration.tc.TCReflection;
|
||||
|
||||
/**
|
||||
* JEI 插件:将源质注册为自定义 IIngredient 类型显示在 JEI 成分列表中。
|
||||
* 拖拽功能由 AE2-JEI-Integration 模组的 GhostIngredientHandler + IngredientConverter 处理。
|
||||
* 不创建虚拟物品,不污染物品注册表。
|
||||
*/
|
||||
@JeiPlugin
|
||||
public class ThEJeiPlugin implements IModPlugin {
|
||||
|
||||
private static final Codec<AspectIngredient> CODEC = ResourceLocation.CODEC.flatXmap(
|
||||
rl -> {
|
||||
Aspect aspect = Aspect.getAspect(rl.getPath());
|
||||
if (aspect != null) return DataResult.success(new AspectIngredient(aspect));
|
||||
return DataResult.error(() -> "Unknown aspect: " + rl);
|
||||
},
|
||||
ai -> {
|
||||
ResourceLocation id = TCReflection.getAspectId(ai.aspect());
|
||||
ResourceLocation result = id != null ? id : ResourceLocation.withDefaultNamespace(ai.aspect().getTag());
|
||||
return DataResult.success(result);
|
||||
}
|
||||
);
|
||||
|
||||
public ThEJeiPlugin() {
|
||||
// 可选集成:AE2-JEI-Integration 存在时注册源质拖拽转换(拖拽进 AE2 筛选槽);
|
||||
// 不存在时跳过——只装 JEI 也能正常使用 JEI 显示/配方转移/ghost 拖拽。
|
||||
try {
|
||||
Class.forName("tamaized.ae2jeiintegration.api.integrations.jei.IngredientConverters");
|
||||
Class.forName("thaumicenergistics.integration.jei.Ae2JeiCompat")
|
||||
.getMethod("register").invoke(null);
|
||||
ThaumicEnergistics.LOG.info("[JEI] AE2-JEI-Integration detected, essentia drag-conversion registered");
|
||||
} catch (ClassNotFoundException e) {
|
||||
ThaumicEnergistics.LOG.info("[JEI] AE2-JEI-Integration not present, essentia drag-conversion unavailable (optional)");
|
||||
} catch (ReflectiveOperationException e) {
|
||||
ThaumicEnergistics.LOG.warn("[JEI] AE2-JEI-Integration registration failed: {}", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceLocation getPluginUid() {
|
||||
return ThaumicEnergistics.id("jei");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerIngredients(IModIngredientRegistration registration) {
|
||||
List<AspectIngredient> ingredients = new ArrayList<>();
|
||||
if (Aspect.aspects != null) {
|
||||
for (Aspect aspect : Aspect.aspects.values()) {
|
||||
if (aspect != null && aspect.getTag() != null) {
|
||||
ingredients.add(new AspectIngredient(aspect));
|
||||
}
|
||||
}
|
||||
}
|
||||
registration.register(
|
||||
AspectIngredientType.INSTANCE,
|
||||
ingredients,
|
||||
AspectIngredientHelper.INSTANCE,
|
||||
AspectIngredientRenderer.INSTANCE,
|
||||
CODEC
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerRecipeTransferHandlers(IRecipeTransferRegistration registration) {
|
||||
ThaumicEnergistics.LOG.info("[JEI] Registering ThEUseCraftingRecipeTransfer for ArcaneCraftingTerminal");
|
||||
// 注册 IRecipeTransferInfo 让 JEI 显示 "+" 按钮
|
||||
registration.addRecipeTransferHandler(new ArcaneCraftingTransferInfo());
|
||||
registration.addRecipeTransferHandler(new ArcaneRecipeTransferInfo());
|
||||
// 注册 IRecipeTransferHandler 让点击 "+" 时走 ME 网络
|
||||
registration.addRecipeTransferHandler(new ThEUseCraftingRecipeTransfer(registration.getTransferHelper()), CRAFTING);
|
||||
registration.addRecipeTransferHandler(new ThEUseArcaneRecipeTransfer(registration.getTransferHelper()), ThaumcraftJeiPlugin.ARCANE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerGuiHandlers(IGuiHandlerRegistration registration) {
|
||||
// 源质元件工作台:从 JEI 拖拽要素到分区格添加分区(纯 JEI API,可选功能)
|
||||
registration.addGhostIngredientHandler(GuiEssentiaCellWorkbench.class, new CellWorkbenchGhostIngredientHandler());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package thaumicenergistics.integration.jei;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
|
||||
import mezz.jei.api.gui.builder.ITooltipBuilder;
|
||||
import mezz.jei.api.gui.ingredient.IRecipeSlotsView;
|
||||
import mezz.jei.api.recipe.RecipeIngredientRole;
|
||||
import mezz.jei.api.recipe.transfer.IRecipeTransferError;
|
||||
import mezz.jei.api.recipe.transfer.IRecipeTransferHandler;
|
||||
import mezz.jei.api.recipe.transfer.IRecipeTransferHandlerHelper;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import appeng.core.network.serverbound.FillCraftingGridFromRecipePacket;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
|
||||
import thaumcraft.integration.jei.JeiArcaneRecipe;
|
||||
import thaumcraft.integration.jei.ThaumcraftJeiPlugin;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import thaumicenergistics.common.container.ContainerArcaneCraftingTerminal;
|
||||
import thaumicenergistics.init.ModMenuTypes;
|
||||
|
||||
/**
|
||||
* 奥术合成终端专用的 JEI 奥术配方转移处理器。
|
||||
* 点击 "+" 时发送 FillCraftingGridFromRecipePacket 从 ME 网络填充材料。
|
||||
*/
|
||||
public class ThEUseArcaneRecipeTransfer
|
||||
implements IRecipeTransferHandler<ContainerArcaneCraftingTerminal, JeiArcaneRecipe> {
|
||||
|
||||
private final IRecipeTransferHandlerHelper helper;
|
||||
|
||||
public ThEUseArcaneRecipeTransfer(IRecipeTransferHandlerHelper helper) {
|
||||
this.helper = helper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ContainerArcaneCraftingTerminal> getContainerClass() {
|
||||
return ContainerArcaneCraftingTerminal.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MenuType<ContainerArcaneCraftingTerminal>> getMenuType() {
|
||||
return Optional.of(ModMenuTypes.ARCANE_CRAFTING_TERMINAL.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public mezz.jei.api.recipe.RecipeType<JeiArcaneRecipe> getRecipeType() {
|
||||
return ThaumcraftJeiPlugin.ARCANE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRecipeTransferError transferRecipe(
|
||||
ContainerArcaneCraftingTerminal menu,
|
||||
JeiArcaneRecipe recipe,
|
||||
IRecipeSlotsView display,
|
||||
net.minecraft.world.entity.player.Player player,
|
||||
boolean maxTransfer,
|
||||
boolean doTransfer) {
|
||||
|
||||
ThaumicEnergistics.LOG.info("[JEI-Arcane] transferRecipe called, recipeId={}, doTransfer={}",
|
||||
recipe.id(), doTransfer);
|
||||
|
||||
boolean craftMissing = AbstractContainerScreen.hasControlDown();
|
||||
|
||||
// 预览:始终 null(绿色按钮),由服务端处理实际缺失
|
||||
if (!doTransfer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 构建模板物品列表(9 格,对应 3x3 合成格)
|
||||
var templateItems = NonNullList.withSize(9, ItemStack.EMPTY);
|
||||
var inputs = recipe.inputs();
|
||||
for (int i = 0; i < templateItems.size() && i < inputs.size(); i++) {
|
||||
var variants = inputs.get(i);
|
||||
if (variants != null && !variants.isEmpty()) {
|
||||
templateItems.set(i, variants.get(0).copyWithCount(1));
|
||||
}
|
||||
}
|
||||
|
||||
// 奥术配方不在 vanilla 配方管理器中,recipeId 传 null
|
||||
// 服务端会用 Ingredient.of(templateItems) 重建,然后从 ME 网络提取
|
||||
var packet = new FillCraftingGridFromRecipePacket(null, templateItems, craftMissing);
|
||||
ThaumicEnergistics.LOG.info("[JEI-Arcane] Sending FillCraftingGridFromRecipePacket, templateCount={}",
|
||||
templateItems.stream().filter(s -> !s.isEmpty()).count());
|
||||
PacketDistributor.sendToServer(packet);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package thaumicenergistics.integration.jei;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.CraftingRecipe;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import net.minecraft.world.item.crafting.RecipeHolder;
|
||||
import net.minecraft.world.item.crafting.RecipeType;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
|
||||
import mezz.jei.api.gui.builder.ITooltipBuilder;
|
||||
import mezz.jei.api.gui.ingredient.IRecipeSlotsView;
|
||||
import mezz.jei.api.recipe.RecipeIngredientRole;
|
||||
import mezz.jei.api.recipe.transfer.IRecipeTransferError;
|
||||
import mezz.jei.api.recipe.transfer.IRecipeTransferHandler;
|
||||
import mezz.jei.api.recipe.transfer.IRecipeTransferHandlerHelper;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import appeng.core.network.serverbound.FillCraftingGridFromRecipePacket;
|
||||
import appeng.util.CraftingRecipeUtil;
|
||||
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import thaumicenergistics.ThaumicEnergistics;
|
||||
import thaumicenergistics.common.container.ContainerArcaneCraftingTerminal;
|
||||
import thaumicenergistics.init.ModMenuTypes;
|
||||
|
||||
import static appeng.integration.modules.itemlists.TransferHelper.BLUE_PLUS_BUTTON_COLOR;
|
||||
import static appeng.integration.modules.itemlists.TransferHelper.BLUE_SLOT_HIGHLIGHT_COLOR;
|
||||
import static appeng.integration.modules.itemlists.TransferHelper.ORANGE_PLUS_BUTTON_COLOR;
|
||||
import static appeng.integration.modules.itemlists.TransferHelper.RED_SLOT_HIGHLIGHT_COLOR;
|
||||
|
||||
/**
|
||||
* 奥术合成终端专用的 JEI 配方转移处理器。
|
||||
* 预览时使用 findMissingIngredients 检查 ME 网络物品可用性,
|
||||
* 实际转移时发送 FillCraftingGridFromRecipePacket 从 ME 网络填充材料。
|
||||
*
|
||||
* 参考 AE2-JEI-Integration 的 UseCraftingRecipeTransfer 实现。
|
||||
*/
|
||||
public class ThEUseCraftingRecipeTransfer
|
||||
implements IRecipeTransferHandler<ContainerArcaneCraftingTerminal, RecipeHolder<CraftingRecipe>> {
|
||||
|
||||
private static final int CRAFTING_GRID_WIDTH = 3;
|
||||
private static final int CRAFTING_GRID_HEIGHT = 3;
|
||||
|
||||
private final IRecipeTransferHandlerHelper helper;
|
||||
|
||||
public ThEUseCraftingRecipeTransfer(IRecipeTransferHandlerHelper helper) {
|
||||
this.helper = helper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ContainerArcaneCraftingTerminal> getContainerClass() {
|
||||
return ContainerArcaneCraftingTerminal.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MenuType<ContainerArcaneCraftingTerminal>> getMenuType() {
|
||||
return Optional.of(ModMenuTypes.ARCANE_CRAFTING_TERMINAL.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public mezz.jei.api.recipe.RecipeType<RecipeHolder<CraftingRecipe>> getRecipeType() {
|
||||
return mezz.jei.api.constants.RecipeTypes.CRAFTING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRecipeTransferError transferRecipe(
|
||||
ContainerArcaneCraftingTerminal menu,
|
||||
RecipeHolder<CraftingRecipe> recipeHolder,
|
||||
IRecipeSlotsView display,
|
||||
net.minecraft.world.entity.player.Player player,
|
||||
boolean maxTransfer,
|
||||
boolean doTransfer) {
|
||||
|
||||
var recipe = recipeHolder.value();
|
||||
ThaumicEnergistics.LOG.info("[JEI] transferRecipe called, recipeId={}, doTransfer={}, player={}",
|
||||
recipeHolder.id(), doTransfer, player.getName().getString());
|
||||
if (recipe.getType() != RecipeType.CRAFTING) {
|
||||
ThaumicEnergistics.LOG.warn("[JEI] Not a crafting recipe, type={}", recipe.getType());
|
||||
return helper.createInternalError();
|
||||
}
|
||||
if (recipe.getIngredients().isEmpty()) {
|
||||
return helper.createInternalError();
|
||||
}
|
||||
if (!recipe.canCraftInDimensions(CRAFTING_GRID_WIDTH, CRAFTING_GRID_HEIGHT)) {
|
||||
return helper.createInternalError();
|
||||
}
|
||||
|
||||
boolean craftMissing = AbstractContainerScreen.hasControlDown();
|
||||
// 自行构建 slot→ingredient 映射(0-8 对应 3x3 合成格),
|
||||
// 不依赖 helper.getGuiSlotIndexToIngredientMap(需要 IRecipeTransferInfo 才能正确工作)
|
||||
var slotToIngredientMap = buildSlotToIngredientMap(recipe);
|
||||
var missingSlots = menu.findMissingIngredients(slotToIngredientMap);
|
||||
ThaumicEnergistics.LOG.info("[JEI] missingSlots: missing={}, craftable={}, total={}",
|
||||
missingSlots.missingSlots().size(), missingSlots.craftableSlots().size(), missingSlots.totalSize());
|
||||
|
||||
// 全部缺失 → 红色,无法点击
|
||||
if (missingSlots.missingSlots().size() == slotToIngredientMap.size()) {
|
||||
ThaumicEnergistics.LOG.info("[JEI] All items missing, returning NO_ITEMS error");
|
||||
var inputSlots = display.getSlotViews(RecipeIngredientRole.INPUT);
|
||||
var missingSlotViews = missingSlots.missingSlots().stream()
|
||||
.map(idx -> idx < inputSlots.size() ? inputSlots.get(idx) : null)
|
||||
.filter(Objects::nonNull)
|
||||
.toList();
|
||||
return helper.createUserErrorForMissingSlots(
|
||||
appeng.core.localization.ItemModText.NO_ITEMS.text(), missingSlotViews);
|
||||
}
|
||||
|
||||
// 预览:检查缺失情况,返回对应的错误渲染
|
||||
if (!doTransfer) {
|
||||
if (missingSlots.totalSize() != 0) {
|
||||
int color = missingSlots.anyMissing() ? ORANGE_PLUS_BUTTON_COLOR : BLUE_PLUS_BUTTON_COLOR;
|
||||
return new MissingSlotsError(missingSlots, craftMissing, color);
|
||||
}
|
||||
// 全部物品都有 → null = 绿色按钮
|
||||
return null;
|
||||
}
|
||||
|
||||
// 实际转移:发送 ME 网络填充包
|
||||
var templateItems = NonNullList.withSize(9, ItemStack.EMPTY);
|
||||
var ingredients = CraftingRecipeUtil.ensure3by3CraftingMatrix(recipe);
|
||||
for (int i = 0; i < ingredients.size() && i < 9; i++) {
|
||||
var ingredient = ingredients.get(i);
|
||||
if (!ingredient.isEmpty()) {
|
||||
var items = ingredient.getItems();
|
||||
if (items.length > 0) {
|
||||
templateItems.set(i, items[0].copyWithCount(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
ResourceLocation recipeId = recipeHolder.id();
|
||||
if (recipeId != null && player.level().getRecipeManager().byKey(recipeId).isEmpty()) {
|
||||
recipeId = null;
|
||||
}
|
||||
|
||||
var packet = new FillCraftingGridFromRecipePacket(recipeId, templateItems, craftMissing);
|
||||
ThaumicEnergistics.LOG.info("[JEI] Sending FillCraftingGridFromRecipePacket, recipeId={}, templateCount={}, craftMissing={}",
|
||||
recipeId, templateItems.stream().filter(s -> !s.isEmpty()).count(), craftMissing);
|
||||
PacketDistributor.sendToServer(packet);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配方构建 3x3 合成格的 slot index → ingredient 映射。
|
||||
* 使用 CraftingRecipeUtil.ensure3by3CraftingMatrix 确保 9 格对齐,
|
||||
* 与实际转移时的索引体系完全一致。
|
||||
*/
|
||||
private static Map<Integer, Ingredient> buildSlotToIngredientMap(CraftingRecipe recipe) {
|
||||
var ingredients = CraftingRecipeUtil.ensure3by3CraftingMatrix(recipe);
|
||||
Map<Integer, Ingredient> map = new HashMap<>();
|
||||
for (int i = 0; i < ingredients.size() && i < 9; i++) {
|
||||
var ing = ingredients.get(i);
|
||||
if (!ing.isEmpty()) {
|
||||
map.put(i, ing);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缺失/可合成物品的预览错误渲染器。
|
||||
* 参考 AE2-JEI-Integration.UseCraftingRecipeTransfer.ErrorRenderer。
|
||||
*/
|
||||
private record MissingSlotsError(
|
||||
ContainerArcaneCraftingTerminal.MissingIngredientSlots indices,
|
||||
boolean craftMissing,
|
||||
int color) implements IRecipeTransferError {
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return Type.COSMETIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getButtonHighlightColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showError(GuiGraphics guiGraphics, int mouseX, int mouseY,
|
||||
IRecipeSlotsView slots, int recipeX, int recipeY) {
|
||||
var poseStack = guiGraphics.pose();
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(recipeX, recipeY, 0);
|
||||
|
||||
var slotViews = slots.getSlotViews(RecipeIngredientRole.INPUT);
|
||||
for (int i = 0; i < slotViews.size(); i++) {
|
||||
var slotView = slotViews.get(i);
|
||||
boolean missing = indices.missingSlots().contains(i);
|
||||
boolean craftable = indices.craftableSlots().contains(i);
|
||||
if (missing || craftable) {
|
||||
slotView.drawHighlight(
|
||||
guiGraphics,
|
||||
missing ? RED_SLOT_HIGHLIGHT_COLOR : BLUE_SLOT_HIGHLIGHT_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
poseStack.popPose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMissingCountHint() {
|
||||
return indices.missingSlots().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getTooltip(ITooltipBuilder tooltip) {
|
||||
int missing = indices.missingSlots().size();
|
||||
int craftable = indices.craftableSlots().size();
|
||||
if (missing > 0) {
|
||||
tooltip.add(net.minecraft.network.chat.Component.translatable(
|
||||
"jei.tooltip.transfer.missing", missing));
|
||||
}
|
||||
if (craftable > 0) {
|
||||
tooltip.add(net.minecraft.network.chat.Component.translatable(
|
||||
"jei.tooltip.transfer.craftable", craftable));
|
||||
}
|
||||
if (craftMissing) {
|
||||
tooltip.add(net.minecraft.network.chat.Component.translatable(
|
||||
"jei.tooltip.transfer.ctrl.click"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user