Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,63 +1,29 @@
package com.soliddowant.gregtechenergistics.integration.jei;

import appeng.container.implementations.ContainerPatternTerm;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableTable;
import com.soliddowant.gregtechenergistics.gui.ExtendedPatternContainer;
import mezz.jei.collect.Table;
import mezz.jei.api.IModPlugin;
import mezz.jei.api.IModRegistry;
import mezz.jei.api.JEIPlugin;
import mezz.jei.api.recipe.transfer.IRecipeTransferHandler;
import mezz.jei.api.recipe.transfer.IRecipeTransferRegistry;
import mezz.jei.config.Constants;
import mezz.jei.recipes.RecipeTransferRegistry;
import net.minecraftforge.fml.relauncher.ReflectionHelper;

@SuppressWarnings({"unused", "rawtypes"})
/**
* JEI Plugin for GregTech Energistics.
*
* Registers the Extended Pattern Terminal handler directly.
*
* Note: The native AE2 Pattern Terminal (ContainerPatternTerm) handler is
* replaced at runtime via Mixin (see RecipeRegistryMixin) to support fluid
* encoding.
*/
@SuppressWarnings({ "unused" })
@JEIPlugin
public class JeiPlugin implements IModPlugin {
@Override
public void register(IModRegistry registry)
{
IRecipeTransferRegistry transferRegistry = registry.getRecipeTransferRegistry();

// If true, some change to JEI broke this integration
if(!(transferRegistry instanceof RecipeTransferRegistry))
return;

RecipeTransferRegistry castedTransferRegistry = (RecipeTransferRegistry) transferRegistry;

// Collect non-ContainerPatternTerm transfer handlers and flag if AE2 has been found
// JEE checks if AE2 has been found, but that's not needed as the JVM would throw an exception on the import
// of ContainerPatternTerm if AE2 was not present
Table<Class<?>, String, IRecipeTransferHandler> collectedRegistry =
collectNonPatternTerminals(castedTransferRegistry.getRecipeTransferHandlers());

collectedRegistry.put(ContainerPatternTerm.class, Constants.UNIVERSAL_RECIPE_TRANSFER_UID,
new RecipeTransferHandler());
collectedRegistry.put(ExtendedPatternContainer.class, Constants.UNIVERSAL_RECIPE_TRANSFER_UID,
new ExtendedRecipeTransferHandler());
ReflectionHelper.setPrivateValue(RecipeTransferRegistry.class,
(RecipeTransferRegistry) registry.getRecipeTransferRegistry(), collectedRegistry,
"recipeTransferHandlers", null);
}

protected static <T, U> Table<Class<?>, T, U> collectNonPatternTerminals(ImmutableTable<Class, T, U> transferHandlers) {
Table<Class<?>, T, U> newRegistry = Table.hashBasedTable();
for (final ImmutableTable.Cell<Class, T, U> currentCell : transferHandlers.cellSet()) {
Class<?> rowKey = currentCell.getRowKey();
if(rowKey == null)
continue;

if (currentCell.getRowKey().equals(ContainerPatternTerm.class)) {
continue;
}

//noinspection ConstantConditions
newRegistry.put(currentCell.getRowKey(), currentCell.getColumnKey(), currentCell.getValue());
}

return newRegistry;
@Override
public void register(IModRegistry registry) {
// Register our Extended Pattern Terminal handler
// This uses the public API and works fine since there's no conflict
registry.getRecipeTransferRegistry().addRecipeTransferHandler(
new ExtendedRecipeTransferHandler(),
Constants.UNIVERSAL_RECIPE_TRANSFER_UID);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.soliddowant.gregtechenergistics.integration.jei;

import java.util.Map.Entry;
import java.util.function.Function;

import javax.annotation.Nonnull;
Expand Down Expand Up @@ -59,7 +58,9 @@ protected void performTransfer(ContainerPatternTerm container, IRecipeLayout rec
// Processing recipes: fill sequentially (ignore slot indices)
performTransferWithSlots(recipeLayout.getItemStacks(), inputItems, outputItems, isCraftingRecipe,
this::getFirstItemStack);
performTransferWithSlots(recipeLayout.getFluidStacks(), inputFluids, outputFluids, isCraftingRecipe,
// For fluids, always use processing mode (sequential fill) to avoid slot
// conflicts
performTransferWithSlots(recipeLayout.getFluidStacks(), inputFluids, outputFluids, false,
this::getFirstFluidStack);

NetworkHandler.ServerHandlerChannel.sendToServer(
Expand All @@ -73,6 +74,9 @@ protected void performTransfer(ContainerPatternTerm container, IRecipeLayout rec

protected <T> void performTransferWithSlots(IGuiIngredientGroup<T> ingredientGroup,
T[] inputs, T[] outputs, boolean preserveSlots, Function<Iterable<T>, T> getFirstStack) {
if (ingredientGroup == null || ingredientGroup.getGuiIngredients() == null)
return;

if (preserveSlots) {
// Crafting mode: preserve exact slot positions (with JEI 1-based offset
// correction)
Expand Down Expand Up @@ -173,6 +177,18 @@ protected static ItemStack createFluidEncoder(FluidStack fluid) {
return fluidEncoder;
}

protected static boolean hasAnyFluids(@Nullable FluidStack[] fluids) {
if (fluids == null)
return false;

for (FluidStack fluid : fluids) {
if (fluid != null && fluid.amount > 0)
return true;
}

return false;
}

public static void transferToTerminal(JEIPacket message, Container con) {
// Get information about the crafting terminal, and do some checks
if (!(con instanceof IContainerCraftingPacket))
Expand All @@ -195,8 +211,13 @@ public static void transferToTerminal(JEIPacket message, Container con) {
if (!(con instanceof ContainerPatternTerm))
return;

// If there are fluids, always use processing mode to avoid slot conflicts in
// crafting mode
boolean hasInputFluids = hasAnyFluids(message.inputFluids);
boolean preserveSlots = message.isCraftingRecipe && !hasInputFluids;

ItemStack[] inputStacks = mergeStacks(message.inputItems, message.inputFluids, inputAreaSize,
message.isCraftingRecipe);
preserveSlots);

for (int i = 0; i < inputStacks.length && i < inputAreaSize; i++) {
ItemStack stack = inputStacks[i];
Expand All @@ -206,8 +227,7 @@ public static void transferToTerminal(JEIPacket message, Container con) {
if (message.isCraftingRecipe)
con.onCraftMatrixChanged(new WrapperInvItemHandler(craftMatrix));
else {
ItemStack[] outputStacks = mergeStacks(message.outputItems, message.outputFluids, outputAreaSize,
message.isCraftingRecipe);
ItemStack[] outputStacks = mergeStacks(message.outputItems, message.outputFluids, outputAreaSize, false);

for (int i = 0; i < outputStacks.length && i < outputAreaSize; i++) {
ItemStack stack = outputStacks[i];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.soliddowant.gregtechenergistics.mixins;

import java.util.List;
import java.util.Set;

import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;

import net.minecraftforge.fml.common.Loader;

/**
* Mixin config plugin that conditionally loads mixins based on mod presence.
* This prevents crashes when optional dependencies like JEI are not installed.
*
* <p>
* This plugin assumes that JEI-specific mixins are placed in a ".jei"
* subpackage
* under the main mixin package. For example, if the mixin package is
* "com.soliddowant.gregtechenergistics.mixins", then JEI mixins should be in
* "com.soliddowant.gregtechenergistics.mixins.jei".
* </p>
*/
public class GregTechEnergisticsMixinPlugin implements IMixinConfigPlugin {

private static final String JEI_PACKAGE_SUFFIX = ".jei.";
private static final String JEI_MOD_ID = "jei";

private String mixinPackage;

@Override
public void onLoad(String mixinPackage) {
this.mixinPackage = mixinPackage;
}

@Override
public String getRefMapperConfig() {
return null;
}

@Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
// Only gate JEI-related mixins behind JEI presence check
// Check if the mixin is in the jei subpackage
String jeiMixinPackage = mixinPackage + JEI_PACKAGE_SUFFIX;
if (mixinClassName.startsWith(jeiMixinPackage)) {
return Loader.isModLoaded(JEI_MOD_ID);
}

// Apply all other mixins unconditionally
return true;
}

@Override
public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) {
// No special target handling needed
}

@Override
public List<String> getMixins() {
// Return null to let the config file handle mixin listing
return null;
}

@Override
public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
// No pre-apply processing needed
}

@Override
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
// No post-apply processing needed
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.soliddowant.gregtechenergistics.mixins.jei;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import com.soliddowant.gregtechenergistics.integration.jei.RecipeTransferHandler;

import appeng.container.implementations.ContainerPatternTerm;
import mezz.jei.api.recipe.IRecipeCategory;
import mezz.jei.api.recipe.transfer.IRecipeTransferHandler;
import mezz.jei.recipes.RecipeRegistry;
import net.minecraft.inventory.Container;

/**
* Mixin to override JEI's recipe transfer handler lookup for
* ContainerPatternTerm.
*
* This is necessary because RecipeRegistry creates an immutable snapshot of
* handlers
* during construction, so runtime modifications via reflection don't work.
* AE2's native
* JEI handler doesn't support fluid encoding, so we need to replace it with our
* own.
*/
@Mixin(value = RecipeRegistry.class, remap = false)
public abstract class RecipeRegistryMixin {

@Unique
private static final RecipeTransferHandler GT_ENERGISTICS_HANDLER = new RecipeTransferHandler();

@Inject(method = "getRecipeTransferHandler", at = @At("RETURN"), cancellable = true, remap = false)
private void injectPatternTermHandler(
Container container,
IRecipeCategory recipeCategory,
CallbackInfoReturnable<IRecipeTransferHandler> cir) {

// Only intercept for ContainerPatternTerm
if (!(container instanceof ContainerPatternTerm)) {
return;
}

IRecipeTransferHandler originalHandler = cir.getReturnValue();

// If there's no handler or it's already ours, do nothing
if (originalHandler == null) {
return;
}

if (originalHandler instanceof RecipeTransferHandler) {
return;
}

// Replace AE2's handler with this mod's handler
cir.setReturnValue(GT_ENERGISTICS_HANDLER);
}
}
4 changes: 3 additions & 1 deletion src/main/resources/mixins.gregtechenergistics.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
"target": "@env(DEFAULT)",
"minVersion": "0.8",
"compatibilityLevel": "JAVA_8",
"plugin": "com.soliddowant.gregtechenergistics.mixins.GregTechEnergisticsMixinPlugin",
"mixins": [
"MetaTileEntityHolderMixin"
],
"client": [
"GuiCraftingStatusMixin",
"ItemEncodedPatternMixin",
"RenderItemOverlayMixin"
"RenderItemOverlayMixin",
"jei.RecipeRegistryMixin"
],
"server": []
}