diff --git a/build.gradle b/build.gradle index 64544ed..007bfa3 100644 --- a/build.gradle +++ b/build.gradle @@ -102,6 +102,10 @@ dependencies { //jec modCompileOnly "curse.maven:just-enough-characters-250702:6680042" + + //mae2 +// modRuntimeOnly "curse.maven:modern-ae2-additions-1028068:6342203" + modCompileOnly "curse.maven:modern-ae2-additions-1028068:6342203" } allprojects { diff --git a/src/main/java/com/extendedae_plus/content/ScaledProcessingPattern.java b/src/main/java/com/extendedae_plus/content/ScaledProcessingPattern.java new file mode 100644 index 0000000..27a7c5a --- /dev/null +++ b/src/main/java/com/extendedae_plus/content/ScaledProcessingPattern.java @@ -0,0 +1,136 @@ +package com.extendedae_plus.content; + +import appeng.api.crafting.IPatternDetails; +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import appeng.api.stacks.KeyCounter; +import appeng.crafting.pattern.AEProcessingPattern; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +/** + * 缩放后的处理样板,结构完全模拟 AEProcessingPattern。 + * 保持 sparse/condensed/inputs 的一致性,同时保存原始样板。 + */ +public final class ScaledProcessingPattern implements IPatternDetails { + + private final AEProcessingPattern original; // 原始样板引用 + private final AEItemKey definition; // 样板物品 + private final GenericStack[] sparseInputs; // 缩放后的稀疏输入 + private final GenericStack[] sparseOutputs; // 缩放后的稀疏输出 + private final IInput[] inputs; // 缩放后的压缩输入 + private final GenericStack[] condensedOutputs; // 缩放后的压缩输出 + + public ScaledProcessingPattern( + AEProcessingPattern original, + AEItemKey definition, + GenericStack[] sparseInputs, + GenericStack[] sparseOutputs, + IInput[] inputs, + GenericStack[] condensedOutputs + ) { + this.original = Objects.requireNonNull(original); + this.definition = Objects.requireNonNull(definition); + this.sparseInputs = Objects.requireNonNull(sparseInputs); + this.sparseOutputs = Objects.requireNonNull(sparseOutputs); + this.inputs = Objects.requireNonNull(inputs); + this.condensedOutputs = Objects.requireNonNull(condensedOutputs); + } + + /* -------------------- API 实现 -------------------- */ + + public AEProcessingPattern getOriginal() { + return original; + } + + @Override + public AEItemKey getDefinition() { + return definition; + } + + @Override + public IInput[] getInputs() { + return inputs; + } + + @Override + public GenericStack[] getOutputs() { + return condensedOutputs; + } + + public GenericStack[] getSparseInputs() { + return sparseInputs; + } + + public GenericStack[] getSparseOutputs() { + return sparseOutputs; + } + + @Override + public GenericStack getPrimaryOutput() { + if (condensedOutputs.length > 0) return condensedOutputs[0]; + return original.getPrimaryOutput(); + } + + @Override + public boolean supportsPushInputsToExternalInventory() { + return original.supportsPushInputsToExternalInventory(); + } + + @Override + public void pushInputsToExternalInventory(KeyCounter[] inputHolder, PatternInputSink inputSink) { + // 保持和 AEProcessingPattern 一致,用 sparseInputs 驱动 + if (sparseInputs.length == inputs.length) { + IPatternDetails.super.pushInputsToExternalInventory(inputHolder, inputSink); + } else { + KeyCounter allInputs = new KeyCounter(); + for (KeyCounter counter : inputHolder) { + allInputs.addAll(counter); + } + for (GenericStack sparseInput : sparseInputs) { + if (sparseInput != null) { + AEKey key = sparseInput.what(); + long amount = sparseInput.amount(); + long available = allInputs.get(key); + if (available < amount) { + throw new RuntimeException("Expected at least %d of %s when pushing scaled pattern, but only %d available" + .formatted(amount, key, available)); + } + inputSink.pushInput(key, amount); + allInputs.remove(key, amount); + } + } + } + } + + /* -------------------- 缩放输入代理 -------------------- */ + + public static final class Input implements IPatternDetails.IInput { + private final GenericStack[] template; + private final long multiplier; + + public Input(GenericStack[] template, long multiplier) { + this.template = template; + this.multiplier = multiplier; + } + + public GenericStack[] getPossibleInputs() { + return this.template; + } + + public long getMultiplier() { + return this.multiplier; + } + + public boolean isValid(AEKey input, Level level) { + return input.matches(this.template[0]); + } + + public @Nullable AEKey getRemainingKey(AEKey template) { + return null; + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/autopattern/CraftingCalculationAccessor.java b/src/main/java/com/extendedae_plus/mixin/autopattern/CraftingCalculationAccessor.java new file mode 100644 index 0000000..96bb300 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/autopattern/CraftingCalculationAccessor.java @@ -0,0 +1,15 @@ +package com.extendedae_plus.mixin.autopattern; + +import appeng.api.stacks.AEKey; +import appeng.crafting.CraftingCalculation; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(CraftingCalculation.class) +public interface CraftingCalculationAccessor { + @Accessor("output") + AEKey extendedae_plus$getOutput(); + + @Accessor("requestedAmount") + long extendedae_plus$getRequestedAmount(); +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/mixin/autopattern/CraftingServiceGetProvidersMixin.java b/src/main/java/com/extendedae_plus/mixin/autopattern/CraftingServiceGetProvidersMixin.java new file mode 100644 index 0000000..5604bbb --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/autopattern/CraftingServiceGetProvidersMixin.java @@ -0,0 +1,28 @@ +package com.extendedae_plus.mixin.autopattern; + +import appeng.api.crafting.IPatternDetails; +import appeng.me.service.CraftingService; +import com.extendedae_plus.content.ScaledProcessingPattern; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +/** + * 在 CraftingService.getProviders 调用点修改传入的 IPatternDetails 参数(回退到网络注册的原始样板) + */ +@Mixin(value = CraftingService.class, remap = false) +public class CraftingServiceGetProvidersMixin { + + @ModifyArg(method = "getProviders(Lappeng/api/crafting/IPatternDetails;)Ljava/lang/Iterable;", + at = @At(value = "INVOKE", target = "Lappeng/me/service/helpers/NetworkCraftingProviders;getMediums(Lappeng/api/crafting/IPatternDetails;)Ljava/lang/Iterable;"), + index = 0) + private IPatternDetails extendedae_plus$modifyGetProvidersArg(IPatternDetails original) { + IPatternDetails base = null; + if (original instanceof ScaledProcessingPattern scaledProcessingPattern) { + base = scaledProcessingPattern.getOriginal(); + } + return base == null ? original : base; + } +} + + diff --git a/src/main/java/com/extendedae_plus/mixin/autopattern/CraftingTreeNodeAccessor.java b/src/main/java/com/extendedae_plus/mixin/autopattern/CraftingTreeNodeAccessor.java new file mode 100644 index 0000000..81d60e2 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/autopattern/CraftingTreeNodeAccessor.java @@ -0,0 +1,16 @@ +package com.extendedae_plus.mixin.autopattern; + +import appeng.api.stacks.AEKey; +import appeng.crafting.CraftingTreeNode; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(CraftingTreeNode.class) +public interface CraftingTreeNodeAccessor { + + @Accessor("what") + AEKey extendedae_plus$getWhat(); + + @Accessor("amount") + long extendedae_plus$getAmount(); +} diff --git a/src/main/java/com/extendedae_plus/mixin/autopattern/CraftingTreeNodeMixin.java b/src/main/java/com/extendedae_plus/mixin/autopattern/CraftingTreeNodeMixin.java new file mode 100644 index 0000000..d792687 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/autopattern/CraftingTreeNodeMixin.java @@ -0,0 +1,30 @@ +package com.extendedae_plus.mixin.autopattern; + +import appeng.api.stacks.KeyCounter; +import appeng.crafting.CraftingTreeNode; +import appeng.crafting.inv.CraftingSimulationState; +import com.extendedae_plus.util.RequestedAmountHolder; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; + +@Mixin(value = CraftingTreeNode.class,remap = false) +public class CraftingTreeNodeMixin { + @Inject(method = "request(Lappeng/crafting/inv/CraftingSimulationState;JLappeng/api/stacks/KeyCounter;)V", + at = @At(value = "INVOKE", + target = "Lappeng/crafting/CraftingTreeNode;addContainerItems(Lappeng/api/stacks/AEKey;JLappeng/api/stacks/KeyCounter;)V"), + locals = LocalCapture.CAPTURE_FAILHARD) + private void captureRequestedAmount(CraftingSimulationState inv, long requestedAmount, KeyCounter containerItems, CallbackInfo ci) { + // push the requestedAmount before addContainerItems is called + RequestedAmountHolder.push(requestedAmount); + } + + @Inject(method = "request(Lappeng/crafting/inv/CraftingSimulationState;JLappeng/api/stacks/KeyCounter;)V", + at = @At(value = "RETURN")) + private void clearRequestedAmountOnReturn(CraftingSimulationState inv, long requestedAmount, KeyCounter containerItems, CallbackInfo ci) { + // pop the pushed requested amount on return + RequestedAmountHolder.pop(); + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/autopattern/CraftingTreeProcessMixin.java b/src/main/java/com/extendedae_plus/mixin/autopattern/CraftingTreeProcessMixin.java new file mode 100644 index 0000000..484b8d8 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/autopattern/CraftingTreeProcessMixin.java @@ -0,0 +1,50 @@ +package com.extendedae_plus.mixin.autopattern; + +import appeng.api.crafting.IPatternDetails; +import appeng.api.networking.crafting.ICraftingService; +import appeng.api.stacks.AEKey; +import appeng.crafting.CraftBranchFailure; +import appeng.crafting.CraftingCalculation; +import appeng.crafting.CraftingTreeNode; +import appeng.crafting.CraftingTreeProcess; +import appeng.crafting.inv.CraftingSimulationState; +import appeng.crafting.pattern.AEProcessingPattern; +import com.extendedae_plus.util.PatternScaler; +import com.extendedae_plus.util.RequestedAmountHolder; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; + +/** + * 注入 CraftingTreeProcess 构造器尾部:将 AEProcessingPattern 替换为 ScaledProcessingPattern + * 以确保后续执行使用放大后的输入/输出视图。 + */ +@Mixin(CraftingTreeProcess.class) +public abstract class CraftingTreeProcessMixin { + + @Shadow abstract void request(CraftingSimulationState inv, long times) throws CraftBranchFailure, InterruptedException; + + @ModifyVariable( + method = "(Lappeng/api/networking/crafting/ICraftingService;Lappeng/crafting/CraftingCalculation;Lappeng/api/crafting/IPatternDetails;Lappeng/crafting/CraftingTreeNode;)V", + at = @At("HEAD"), + argsOnly = true + ) + private static IPatternDetails extendedae_plus$replaceDetailsAtHead(IPatternDetails original, ICraftingService cc, CraftingCalculation job, IPatternDetails details, CraftingTreeNode craftingTreeNode) { + try { + if (!(details instanceof AEProcessingPattern proc)) return original; + + CraftingTreeNodeAccessor parentAcc = (CraftingTreeNodeAccessor) craftingTreeNode; + AEKey parentTarget = parentAcc.extendedae_plus$getWhat(); + long requested = RequestedAmountHolder.get(); + // 使用当前线程栈顶的值进行缩放,不在此处清理;构造完成后应该由调用方的 pop 恢复状态 + return PatternScaler.scale(proc, parentTarget, requested); + } catch (Exception e) { + LOGGER.warn("构建倍增样板出错", e); + e.printStackTrace(); + return original; + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/autopattern/PatternProviderLogicContainsRedirectMixin.java b/src/main/java/com/extendedae_plus/mixin/autopattern/PatternProviderLogicContainsRedirectMixin.java new file mode 100644 index 0000000..2bb8107 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/autopattern/PatternProviderLogicContainsRedirectMixin.java @@ -0,0 +1,39 @@ +package com.extendedae_plus.mixin.autopattern; + +import appeng.api.crafting.IPatternDetails; +import appeng.helpers.patternprovider.PatternProviderLogic; +import com.extendedae_plus.content.ScaledProcessingPattern; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.List; + +/** + * Redirect PatternProviderLogic.pushPattern 中对 List.contains 的调用, + * 在遇到缩放样板时回退匹配到原始样板实例。 + */ +@Mixin(value = PatternProviderLogic.class, remap = false) +public class PatternProviderLogicContainsRedirectMixin { + + @Redirect(method = "pushPattern", + at = @At( + value = "INVOKE", + target = "Ljava/util/List;contains(Ljava/lang/Object;)Z") + ) + private boolean extendedae_plus$patternsContains(List list, Object o) { + try { + if (o instanceof ScaledProcessingPattern scaled) { + IPatternDetails base = scaled.getOriginal(); + if (base != null && list.indexOf(base) != -1) { + return true; + } + } + // 使用 indexOf 避免再次触发对 List.contains 的 redirect(防止递归) + return list.indexOf(o) != -1; + } catch (Throwable t) { + t.printStackTrace(); + return list.indexOf(o) != -1; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/util/ArraySimplifier.java b/src/main/java/com/extendedae_plus/util/ArraySimplifier.java new file mode 100644 index 0000000..076eb31 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/ArraySimplifier.java @@ -0,0 +1,78 @@ +package com.extendedae_plus.util; + +import java.util.Arrays; + +public class ArraySimplifier { + + // 计算两个数的GCD using Euclidean algorithm (long版本) + public static long gcd(long a, long b) { + while (b != 0) { + long temp = b; + b = a % b; + a = temp; + } + return a; + } + + // 计算整个数组的GCD + public static long findGCD(long[] arr) { + if (arr.length == 0) { + return 0; + } + long result = arr[0]; + for (int i = 1; i < arr.length; i++) { + result = gcd(result, arr[i]); + // 如果已经找到GCD为1,可以提前终止 + if (result == 1) { + break; + } + } + return result; + } + + // 简化数组:每个元素除以数组的GCD + public static long[] simplifyFraction(long[] arr) { + if (arr.length == 0) { + return new long[0]; + } + long gcd = findGCD(arr); + if (gcd == 0) { + // 如果GCD为0(所有元素为0),返回原数组的副本 + return Arrays.copyOf(arr, arr.length); + } + long[] simplified = new long[arr.length]; + for (int i = 0; i < arr.length; i++) { + simplified[i] = arr[i] / gcd; + } + return simplified; + } + + // 将两个数组合并为一个新数组(先放 a 后放 b) + public static long[] combine(long[] a, long[] b) { + long[] out = new long[a.length + b.length]; + System.arraycopy(a, 0, out, 0, a.length); + System.arraycopy(b, 0, out, a.length, b.length); + return out; + } + + // 寻找数组的 GCD,遇到 1 则立即返回 1(早期退出优化) + public static long findGCDWithEarlyExit(long[] arr) { + if (arr.length == 0) return 0; + long result = 0; + for (long v : arr) { + if (v == 1) return 1; // already irreducible + if (v == 0) continue; + if (result == 0) result = v; else result = gcd(result, v); + if (result == 1) return 1; + } + return result == 0 ? 0 : Math.abs(result); + } + + // 根据给定的 gcd 返回一个已除以 gcd 的新数组;如果 gcd==1 返回原数组(避免不必要的分配) + public static long[] simplifyByGcd(long[] arr, long gcd) { + if (gcd <= 1) return arr; + long[] out = new long[arr.length]; + for (int i = 0; i < arr.length; i++) out[i] = arr[i] / gcd; + return out; + } +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/util/PatternScaler.java b/src/main/java/com/extendedae_plus/util/PatternScaler.java new file mode 100644 index 0000000..dc15972 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/PatternScaler.java @@ -0,0 +1,114 @@ +package com.extendedae_plus.util; + +import appeng.api.crafting.IPatternDetails.IInput; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import appeng.crafting.pattern.AEProcessingPattern; +import com.extendedae_plus.content.ScaledProcessingPattern; + +import java.util.Arrays; + +import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; + +public final class PatternScaler { + private PatternScaler() { + } + + public static ScaledProcessingPattern scale(AEProcessingPattern base, AEKey target, long requestedAmount) { + if (base == null) throw new IllegalArgumentException("base"); + if (target == null) throw new IllegalArgumentException("target"); + + GenericStack[] baseSparseInputs = base.getSparseInputs(); + GenericStack[] baseSparseOutputs = base.getSparseOutputs(); + IInput[] baseInputs = base.getInputs(); + GenericStack[] baseOutputs = base.getOutputs(); + + // 新逻辑:不再对样板进行单位化处理 + // 找到目标输出在 outputs 中的索引(尝试匹配 target,否则取第一个非空输出) + int targetOutIndex = -1; + for (int i = 0; i < baseOutputs.length; i++) { + var out = baseOutputs[i]; + if (out != null && target != null && out.what() != null && out.what().equals(target)) { + targetOutIndex = i; + break; + } + } + if (targetOutIndex == -1) { + for (int i = 0; i < baseOutputs.length; i++) { + if (baseOutputs[i] != null) { + targetOutIndex = i; + break; + } + } + } + if (targetOutIndex == -1 && baseOutputs.length > 0) targetOutIndex = 0; + + long perOperationTarget = 1L; + if (targetOutIndex >= 0 && baseOutputs[targetOutIndex] != null) { + long amt = baseOutputs[targetOutIndex].amount(); + if (amt > 0) perOperationTarget = amt; + } + + // 使用最小整数倍(ceil)策略:直接选择满足请求的最小倍数 + long multiplier = 1L; + if (requestedAmount > 0) { + long needed = requestedAmount / perOperationTarget + ((requestedAmount % perOperationTarget) == 0 ? 0 : 1); + multiplier = needed <= 1L ? 1L : needed; + } + + // 构建压缩输入(将每个输入的 multiplier 翻倍,保留每个模板的原始数量) + IInput[] scaledInputs = new IInput[baseInputs.length]; + for (int i = 0; i < baseInputs.length; i++) { + var in = baseInputs[i]; + var template = in.getPossibleInputs(); + GenericStack[] scaledTemplates = new GenericStack[template.length]; + for (int j = 0; j < template.length; j++) { + scaledTemplates[j] = new GenericStack(template[j].what(), template[j].amount()); + } + scaledInputs[i] = new ScaledProcessingPattern.Input(scaledTemplates, in.getMultiplier() * multiplier); + } + + /* 4. 构建压缩输出 */ + GenericStack[] scaledCondensedOutputs = new GenericStack[baseOutputs.length]; + for (int i = 0; i < baseOutputs.length; i++) { + GenericStack out = baseOutputs[i]; + if (out != null) { + scaledCondensedOutputs[i] = new GenericStack(out.what(), out.amount() * multiplier); + } + } + + // 构建并打印稀疏表示(直接按 multiplier 放大) + GenericStack[] scaledSparseInputs = new GenericStack[baseSparseInputs.length]; + for (int i = 0; i < baseSparseInputs.length; i++) { + var in = baseSparseInputs[i]; + if (in != null) { + scaledSparseInputs[i] = new GenericStack(in.what(), in.amount() * multiplier); + } + } + GenericStack[] scaledSparseOutputs = new GenericStack[baseSparseOutputs.length]; + for (int i = 0; i < baseSparseOutputs.length; i++) { + var out = baseSparseOutputs[i]; + if (out != null) { + scaledSparseOutputs[i] = new GenericStack(out.what(), out.amount() * multiplier); + } + } + + + /* Debug 输出 */ + LOGGER.info("[extendedae_plus] 正在缩放样板: 目标物品: {} 请求数量: {} 缩放后输入: {} 缩放后输出: {} 缩放后稀疏输入: {} 缩放后稀疏输出: {}", + target, + requestedAmount, + Arrays.toString(scaledInputs), + Arrays.toString(scaledCondensedOutputs), + Arrays.toString(scaledSparseInputs), + Arrays.toString(scaledSparseOutputs)); + + + return new ScaledProcessingPattern(base, + base.getDefinition(), + scaledSparseInputs, + scaledSparseOutputs, + scaledInputs, + scaledCondensedOutputs); + } +} diff --git a/src/main/java/com/extendedae_plus/util/RequestedAmountHolder.java b/src/main/java/com/extendedae_plus/util/RequestedAmountHolder.java new file mode 100644 index 0000000..e70187a --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/RequestedAmountHolder.java @@ -0,0 +1,44 @@ +package com.extendedae_plus.util; + +import java.util.ArrayDeque; +import java.util.Deque; + +/** + * Thread-local stack holder for requested amounts to support nested requests. + */ +public final class RequestedAmountHolder { + private static final ThreadLocal> HOLDER = ThreadLocal.withInitial(ArrayDeque::new); + + private RequestedAmountHolder() { + } + + /** + * Push a requested amount onto the thread-local stack. + */ + public static void push(long v) { + Deque dq = HOLDER.get(); + dq.push(v); + } + + /** + * Pop the top value from the thread-local stack. Safe if empty. + */ + public static void pop() { + Deque dq = HOLDER.get(); + if (dq.isEmpty()) { + return; + } + dq.pop(); + } + + /** + * Peek the current requested amount or return 0 if none. + */ + public static long get() { + Deque dq = HOLDER.get(); + Long v = dq.peek(); + return v == null ? 0L : v; + } +} + + diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index e515bd3..7a916e8 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -22,11 +22,12 @@ "extendedae.HighlightButtonMixin", "extendedae.accessor.GuiExPatternTerminalAccessor", "extendedae.accessor.GuiExPatternTerminalSlotsRowAccessor", - "jei.EncodePatternTransferHandlerMixin", - "hooks.ModelBakeryMixin" + "hooks.ModelBakeryMixin", + "jei.EncodePatternTransferHandlerMixin" ], "mixins": [ "ae2.ContainerPatternEncodingTermMenuMixin", + "ae2.CraftingCPUClusterMixin", "ae2.MEStorageMenuMixin", "ae2.PatternEncodingTermMenuMixin", "ae2.PatternProviderLogicAdvancedMixin", @@ -37,10 +38,15 @@ "ae2.accessor.PatternProviderLogicPatternInputsAccessor", "ae2.accessor.PatternProviderMenuAdvancedAccessor", "ae2WTlib.ContainerUWirelessExPatternTerminalMixin", + "autopattern.CraftingCalculationAccessor", + "autopattern.CraftingServiceGetProvidersMixin", + "autopattern.CraftingTreeNodeAccessor", + "autopattern.CraftingTreeNodeMixin", + "autopattern.CraftingTreeProcessMixin", + "autopattern.PatternProviderLogicContainsRedirectMixin", "extendedae.ContainerExPatternProviderMixin", "extendedae.ContainerExPatternTerminalMixin", "extendedae.ContainerWirelessExPatternTerminalMixin", - "ae2.CraftingCPUClusterMixin", "extendedae.PartExPatternProviderMixin", "extendedae.TileExPatternProviderMixin" ],