diff --git a/src/main/java/com/extendedae_plus/api/crafting/ScaledProcessingPattern.java b/src/main/java/com/extendedae_plus/api/crafting/ScaledProcessingPattern.java index 6c3911d..d279a9d 100644 --- a/src/main/java/com/extendedae_plus/api/crafting/ScaledProcessingPattern.java +++ b/src/main/java/com/extendedae_plus/api/crafting/ScaledProcessingPattern.java @@ -7,10 +7,10 @@ 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.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Objects; @@ -18,126 +18,163 @@ import java.util.Objects; * 缩放后的处理样板,结构完全模拟 AEProcessingPattern。 * 保持 sparse/condensed/inputs 的一致性,同时保存原始样板。 */ -public final class ScaledProcessingPattern implements IPatternDetails { +public class ScaledProcessingPattern implements IPatternDetails { + protected final @NotNull IPatternDetails original; + protected final long multiplier; - private final AEProcessingPattern original; // 原始样板引用 - private final AEItemKey definition; // 样板物品 - private final List sparseInputs; // 缩放后的稀疏输入(List 以适配 1.21 API) - private final List sparseOutputs; // 缩放后的稀疏输出(List 以适配 1.21 API) - private final IInput[] inputs; // 缩放后的压缩输入 - private final List condensedOutputs; // 缩放后的压缩输出(List 以适配 1.21 API) - - public ScaledProcessingPattern( - AEProcessingPattern original, - AEItemKey definition, - List sparseInputs, - List sparseOutputs, - IInput[] inputs, - List condensedOutputs - ) { - this.original = Objects.requireNonNull(original); - this.definition = Objects.requireNonNull(definition); - this.sparseInputs = Collections.unmodifiableList(new ArrayList<>(Objects.requireNonNull(sparseInputs))); - this.sparseOutputs = Collections.unmodifiableList(new ArrayList<>(Objects.requireNonNull(sparseOutputs))); - this.inputs = Objects.requireNonNull(inputs); - this.condensedOutputs = Collections.unmodifiableList(new ArrayList<>(Objects.requireNonNull(condensedOutputs))); + public ScaledProcessingPattern(@NotNull IPatternDetails original, long multiplier) { + if (multiplier <= 0) throw new IllegalArgumentException("multiplier must be > 0"); + this.original = original; + this.multiplier = multiplier; } - /* -------------------- API 实现 -------------------- */ - - public AEProcessingPattern getOriginal() { - return this.original; - } + public @NotNull IPatternDetails getOriginal() {return this.original;} @Override public AEItemKey getDefinition() { - return this.definition; + return this.original.getDefinition(); } @Override public IInput[] getInputs() { - return this.inputs; - } - - @Override - public GenericStack getPrimaryOutput() { - if (!this.condensedOutputs.isEmpty()) return this.condensedOutputs.get(0); - return this.original.getPrimaryOutput(); + IInput[] original = this.original.getInputs(); + IInput[] scaled = new IInput[original.length]; + for (int i = 0; i < original.length; i++) { + scaled[i] = new ScaledInput(original[i], this.multiplier); + } + return scaled; } @Override public List getOutputs() { - return this.condensedOutputs; + var original = this.original.getOutputs(); + List scaled = new ArrayList<>(original.size()); + for (GenericStack g : original) { + if (g != null) { + scaled.add(new GenericStack(g.what(), g.amount() * this.multiplier)); + } + } + return scaled; } @Override - public boolean supportsPushInputsToExternalInventory() { - return this.original.supportsPushInputsToExternalInventory(); - } + public void pushInputsToExternalInventory(KeyCounter[] inputHolder, PatternInputSink sink) { + // 如果 sparseInputs 与 inputs 一一对应,则无需 reorder + if (((AEProcessingPattern) this.original).getSparseInputs().size() == this.original.getInputs().length) { + // AEProcessingPattern 的默认逻辑 + IPatternDetails.super.pushInputsToExternalInventory(inputHolder, sink); + return; + } - @Override - public void pushInputsToExternalInventory(KeyCounter[] inputHolder, PatternInputSink inputSink) { - // 保持和 AEProcessingPattern 一致,用 sparseInputs 驱动 - if (this.sparseInputs.size() == this.inputs.length) { - IPatternDetails.super.pushInputsToExternalInventory(inputHolder, inputSink); - } else { - KeyCounter allInputs = new KeyCounter(); - for (KeyCounter counter : inputHolder) { - allInputs.addAll(counter); - } - for (GenericStack sparseInput : this.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); - } + // 否则必须按 sparse 输入顺序推送 + var allInputs = new KeyCounter(); + for (var ctr : inputHolder) { + allInputs.addAll(ctr); + } + + var sparse = this.getSparseInputs(); // 使用已缩放倍率的顺序表 + + for (var sparseInput : sparse) { + if (sparseInput == null) continue; + + var key = sparseInput.what(); + long amount = sparseInput.amount(); + + long available = allInputs.get(key); + if (available < amount) { + throw new IllegalStateException( + "Expected " + amount + " of " + key + " but only " + available + " available" + ); } + + sink.pushInput(key, amount); + allInputs.remove(key, amount); } } - public List getSparseInputs() { - return this.sparseInputs; + protected List getSparseInputs() { + var original = ((AEProcessingPattern) this.original).getSparseInputs(); + List scaled = new ArrayList<>(original.size()); + for (GenericStack g : original) { + if (g != null) { + scaled.add(new GenericStack(g.what(), g.amount() * this.multiplier)); + } else { + scaled.add(null); // 保持 null 位 + } + } + return scaled; } public List getSparseOutputs() { - return this.sparseOutputs; + var original = ((AEProcessingPattern) this.original).getSparseOutputs(); + List scaled = new ArrayList<>(original.size()); + for (GenericStack g : original) { + if (g != null) { + scaled.add(new GenericStack(g.what(), g.amount() * this.multiplier)); + } else { + scaled.add(null); + } + } + return scaled; } - /* -------------------- 缩放输入代理 -------------------- */ + // equals / hashCode 必须包含 multiplier!不同倍率 = 不同 key + @Override + public int hashCode() { + int h = this.original.hashCode(); + h = 31 * h + Long.hashCode(this.multiplier); + return h; + } - public static final class Input implements IPatternDetails.IInput { - private final GenericStack[] template; - private final long multiplier; + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof ScaledProcessingPattern sp)) return false; + return sp.original.equals(this.original) && sp.multiplier == this.multiplier; + } - public Input(GenericStack[] template, long multiplier) { - this.template = template; - this.multiplier = multiplier; - } + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Scaled[Mult=") + .append(this.multiplier) + .append("] "); + + sb.append("Inputs: ["); + sb.append(String.join(", ", + java.util.Arrays.stream(this.original.getInputs()) + .filter(i -> i.getPossibleInputs() != null && i.getPossibleInputs().length > 0) + .map(i -> { + GenericStack stack = i.getPossibleInputs()[0]; + return stack.what() + "×" + i.getMultiplier(); + }) + .toArray(String[]::new) + )); + sb.append("] "); + + sb.append("Outputs: ["); + sb.append(String.join(", ", + this.original.getOutputs().stream() + .filter(Objects::nonNull) + .map(s -> s.what() + "×" + s.amount()) + .toArray(String[]::new) + )); + sb.append("]"); + + return sb.toString(); + } + + + private record ScaledInput(IInput original, long multiplier) implements IInput { + @Override + public GenericStack[] getPossibleInputs() {return this.original.getPossibleInputs();} @Override - public GenericStack[] getPossibleInputs() { - return this.template; - } + public long getMultiplier() {return this.original.getMultiplier() * this.multiplier;} @Override - public long getMultiplier() { - return this.multiplier; - } + public boolean isValid(AEKey input, Level level) {return this.original.isValid(input, level);} @Override - public boolean isValid(AEKey input, Level level) { - return input.matches(this.template[0]); - } - - @Override - public @Nullable AEKey getRemainingKey(AEKey template) { - return null; - } + public @Nullable AEKey getRemainingKey(AEKey template) {return this.original.getRemainingKey(template);} } } diff --git a/src/main/java/com/extendedae_plus/api/crafting/ScaledProcessingPatternAdv.java b/src/main/java/com/extendedae_plus/api/crafting/ScaledProcessingPatternAdv.java new file mode 100644 index 0000000..efd5f0c --- /dev/null +++ b/src/main/java/com/extendedae_plus/api/crafting/ScaledProcessingPatternAdv.java @@ -0,0 +1,101 @@ +package com.extendedae_plus.api.crafting; + +import appeng.api.crafting.IPatternDetails; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import appeng.api.stacks.KeyCounter; +import net.minecraft.core.Direction; +import net.pedroksl.advanced_ae.common.patterns.AdvProcessingPattern; +import net.pedroksl.advanced_ae.common.patterns.IAdvPatternDetails; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; + +/** + * Advanced AE 扩展版,额外实现 AdvPatternDetails 接口。 + * 仅在 Advanced AE 加载时使用。 + */ +public final class ScaledProcessingPatternAdv extends ScaledProcessingPattern implements IPatternDetails, IAdvPatternDetails { + private final LinkedHashMap dirMap; + + public ScaledProcessingPatternAdv(@NotNull IPatternDetails original, long multiplier) { + super(original, multiplier); + this.dirMap = ((AdvProcessingPattern) original).getDirectionMap(); + } + + @Override + public @NotNull AdvProcessingPattern getOriginal() {return (AdvProcessingPattern) this.original;} + + @Override + public void pushInputsToExternalInventory(KeyCounter[] inputHolder, PatternInputSink sink) { + // 如果 sparseInputs 与 inputs 一一对应,则无需 reorder + if (((AdvProcessingPattern) this.original).getSparseInputs().size() == this.original.getInputs().length) { + // AEProcessingPattern 的默认逻辑 + this.original.pushInputsToExternalInventory(inputHolder, sink); + return; + } + + // 否则必须按 sparse 输入顺序推送 + var allInputs = new KeyCounter(); + for (var counter : inputHolder) allInputs.addAll(counter); + + for (var sparseInput : this.getSparseInputs()) { + if (sparseInput == null) continue; + var key = sparseInput.what(); + long amount = sparseInput.amount(); + long available = allInputs.get(key); + if (available < amount) { + throw new IllegalStateException( + "Expected " + amount + " of " + key + " but only " + available + " available" + ); + } + sink.pushInput(key, amount); + allInputs.remove(key, amount); + } + } + + @Override + protected List getSparseInputs() { + var original = ((AdvProcessingPattern) this.original).getSparseInputs(); + List scaled = new ArrayList<>(original.size()); + for (GenericStack g : original) { + if (g != null) { + scaled.add(new GenericStack(g.what(), g.amount() * this.multiplier)); + } else { + scaled.add(null); // 保持 null 位 + } + } + return scaled; + } + + @Override + public List getSparseOutputs() { + var original = ((AdvProcessingPattern) this.original).getSparseOutputs(); + List scaled = new ArrayList<>(original.size()); + for (GenericStack g : original) { + if (g != null) { + scaled.add(new GenericStack(g.what(), g.amount() * this.multiplier)); + } else { + scaled.add(null); + } + } + return scaled; + } + + @Override + public boolean directionalInputsSet() { + return this.dirMap != null && !this.dirMap.isEmpty(); + } + + @Override + public LinkedHashMap getDirectionMap() { + return this.dirMap; + } + + @Override + public Direction getDirectionSideForInputKey(AEKey key) { + return this.dirMap.get(key); + } +} diff --git a/src/main/java/com/extendedae_plus/api/smartDoubling/ISmartDoublingAwarePattern.java b/src/main/java/com/extendedae_plus/api/smartDoubling/ISmartDoublingAwarePattern.java index c62dc81..8e72828 100644 --- a/src/main/java/com/extendedae_plus/api/smartDoubling/ISmartDoublingAwarePattern.java +++ b/src/main/java/com/extendedae_plus/api/smartDoubling/ISmartDoublingAwarePattern.java @@ -3,4 +3,9 @@ package com.extendedae_plus.api.smartDoubling; public interface ISmartDoublingAwarePattern { boolean eap$allowScaling(); void eap$setAllowScaling(boolean allow); + + // 翻倍限制:0 表示无限制 + int eap$getMultiplierLimit(); + + void eap$setMultiplierLimit(int limit); } diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java index 9f7add1..0a6b6f4 100644 --- a/src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/AdvPatternProviderLogicContainsRedirectMixin.java @@ -25,7 +25,7 @@ public class AdvPatternProviderLogicContainsRedirectMixin { try { if (o instanceof ScaledProcessingPattern scaled) { IPatternDetails base = scaled.getOriginal(); - if (base != null && list.indexOf(base) != -1) { + if (list.indexOf(base) != -1) { return true; } } diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/AdvProcessingPatternMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/AdvProcessingPatternMixin.java new file mode 100644 index 0000000..8e15717 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/AdvProcessingPatternMixin.java @@ -0,0 +1,34 @@ +package com.extendedae_plus.mixin.advancedae; + +import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern; +import net.pedroksl.advanced_ae.common.patterns.AdvProcessingPattern; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(value = AdvProcessingPattern.class, remap = false) +public class AdvProcessingPatternMixin implements ISmartDoublingAwarePattern { + @Unique + private boolean eap$allowScaling = false; // 默认不允许缩放 + @Unique + private int eap$multiplierLimit = 0; // 模式级别的倍数上限,0 表示不限制 + + @Override + public boolean eap$allowScaling() { + return this.eap$allowScaling; + } + + @Override + public void eap$setAllowScaling(boolean allow) { + this.eap$allowScaling = allow; + } + + @Override + public int eap$getMultiplierLimit() { + return this.eap$multiplierLimit; + } + + @Override + public void eap$setMultiplierLimit(int limit) { + this.eap$multiplierLimit = Math.max(0, limit); + } +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java index 909e3a0..b985f76 100644 --- a/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java @@ -1,7 +1,6 @@ package com.extendedae_plus.mixin.advancedae.helpers; import appeng.api.crafting.IPatternDetails; -import appeng.crafting.pattern.AEProcessingPattern; import com.extendedae_plus.api.smartDoubling.ISmartDoubling; import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern; import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderLogicPatternsAccessor; @@ -35,7 +34,7 @@ public class AdvPatternProviderLogicDoublingMixin implements ISmartDoubling { try { var list = ((AdvPatternProviderLogicPatternsAccessor) this).eap$patterns(); for (IPatternDetails details : list) { - if (details instanceof AEProcessingPattern proc && proc instanceof ISmartDoublingAwarePattern aware) { + if (details instanceof ISmartDoublingAwarePattern aware) { aware.eap$setAllowScaling(value); } } @@ -63,7 +62,7 @@ public class AdvPatternProviderLogicDoublingMixin implements ISmartDoubling { var list = ((AdvPatternProviderLogicPatternsAccessor) this).eap$patterns(); boolean allow = this.eap$smartDoubling; for (IPatternDetails details : list) { - if (details instanceof AEProcessingPattern proc && proc instanceof ISmartDoublingAwarePattern aware) { + if (details instanceof ISmartDoublingAwarePattern aware) { aware.eap$setAllowScaling(allow); } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/AEProcessingPatternMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/AEProcessingPatternMixin.java index 54c1437..7dac39e 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/AEProcessingPatternMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/AEProcessingPatternMixin.java @@ -9,6 +9,8 @@ import org.spongepowered.asm.mixin.Unique; public class AEProcessingPatternMixin implements ISmartDoublingAwarePattern { @Unique private boolean eap$allowScaling = false; // 默认不允许缩放 + @Unique + private int eap$multiplierLimit = 0; // 模式级别的倍数上限,0 表示不限制 @Override public boolean eap$allowScaling() { @@ -19,4 +21,14 @@ public class AEProcessingPatternMixin implements ISmartDoublingAwarePattern { public void eap$setAllowScaling(boolean allow) { this.eap$allowScaling = allow; } + + @Override + public int eap$getMultiplierLimit() { + return this.eap$multiplierLimit; + } + + @Override + public void eap$setMultiplierLimit(int limit) { + this.eap$multiplierLimit = Math.max(0, limit); + } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingCalculationMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingCalculationMixin.java new file mode 100644 index 0000000..770e0c3 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingCalculationMixin.java @@ -0,0 +1,30 @@ +package com.extendedae_plus.mixin.ae2.autopattern; + +import appeng.api.networking.IGrid; +import appeng.api.networking.crafting.CalculationStrategy; +import appeng.api.networking.crafting.ICraftingSimulationRequester; +import appeng.api.stacks.GenericStack; +import appeng.crafting.CraftingCalculation; +import com.extendedae_plus.api.smartDoubling.ICraftingCalculationExt; +import net.minecraft.world.level.Level; +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.CallbackInfo; + +@SuppressWarnings({"AddedMixinMembersNamePattern"}) +@Mixin(value = CraftingCalculation.class, remap = false) +public class CraftingCalculationMixin implements ICraftingCalculationExt { + @Unique private IGrid grid; + + @Inject(method = "",at = @At("RETURN")) + private void init(Level level, IGrid grid, ICraftingSimulationRequester simRequester, GenericStack output, CalculationStrategy strategy, CallbackInfo ci) { + this.grid = grid; + } + + @Override + public IGrid getGrid() { + return grid; + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingSimulationStateAccessor.java b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingSimulationStateAccessor.java new file mode 100644 index 0000000..7aef8d5 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingSimulationStateAccessor.java @@ -0,0 +1,14 @@ +package com.extendedae_plus.mixin.ae2.autopattern; + +import appeng.api.crafting.IPatternDetails; +import appeng.crafting.inv.CraftingSimulationState; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(value = CraftingSimulationState.class,remap = false) +public interface CraftingSimulationStateAccessor { + @Accessor("crafts") + Map getCrafts(); +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingSimulationStateMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingSimulationStateMixin.java new file mode 100644 index 0000000..d602383 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingSimulationStateMixin.java @@ -0,0 +1,109 @@ +package com.extendedae_plus.mixin.ae2.autopattern; + +import appeng.api.crafting.IPatternDetails; +import appeng.crafting.CraftingCalculation; +import appeng.crafting.CraftingPlan; +import appeng.crafting.inv.CraftingSimulationState; +import appeng.me.service.CraftingService; +import com.extendedae_plus.api.smartDoubling.ICraftingCalculationExt; +import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern; +import com.extendedae_plus.config.ModConfigs; +import com.extendedae_plus.util.smartDoubling.PatternScaler; +import com.google.common.collect.Iterables; +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.CallbackInfoReturnable; + +import java.util.LinkedHashMap; +import java.util.Map; + +@Mixin(value = CraftingSimulationState.class, remap = false) +public abstract class CraftingSimulationStateMixin { + /** + * 替换 CraftingPlan 构建逻辑,在此统一处理样板倍率 + */ + @Inject(method = "buildCraftingPlan", at = @At("HEAD")) + private static void onBuildCraftingPlan(CraftingSimulationState state, + CraftingCalculation calculation, + long calculatedAmount, + CallbackInfoReturnable cir) { + CraftingSimulationStateAccessor accessor = (CraftingSimulationStateAccessor) state; + Map crafts = accessor.getCrafts(); + // 存放最终分配后的 crafts + Map finalCrafts = new LinkedHashMap<>(); + + for (Map.Entry entry : crafts.entrySet()) { + IPatternDetails processingPattern = entry.getKey(); + long totalAmount = entry.getValue(); + + // 非 AEProcessingPattern 直接保留 + if (!(processingPattern instanceof ISmartDoublingAwarePattern aware)) { + finalCrafts.put(processingPattern, totalAmount); + continue; + } + + boolean allowScaling = aware.eap$allowScaling(); + int perCraftLimit = aware.eap$getMultiplierLimit(); + + + if (!allowScaling || totalAmount <= 1) { + finalCrafts.put(processingPattern, totalAmount); + continue; + } + + if (perCraftLimit <= 0 && ModConfigs.SMART_SCALING_MAX_MULTIPLIER.get() > 0) { + perCraftLimit = ModConfigs.SMART_SCALING_MAX_MULTIPLIER.get(); + } + + if (perCraftLimit <= 0) { + // 检查是否开启 provider 轮询分配功能 + if (ModConfigs.PROVIDER_ROUND_ROBIN_ENABLE.getRaw()) { + CraftingService craftingService = (CraftingService) ((ICraftingCalculationExt) calculation).getGrid().getCraftingService(); + int providerCount = Math.max(Iterables.size(craftingService.getProviders(processingPattern)), 1); + + // totalAmount < providerCount → 只激活 totalAmount 台 provider + if (totalAmount < providerCount) { + providerCount = (int) totalAmount; + } + + long base = totalAmount / providerCount; + long remainder = totalAmount % providerCount; + + // base+1 组(数量 remainder 个) + if (remainder > 0) { + IPatternDetails scaledPlus = PatternScaler.createScaled(processingPattern, base + 1); + finalCrafts.merge(scaledPlus, remainder, Long::sum); + } + + // base 组(数量 providerCount - remainder 个) + long countBase = providerCount - remainder; + if (countBase > 0) { + IPatternDetails scaledBase = PatternScaler.createScaled(processingPattern, base); + finalCrafts.merge(scaledBase, countBase, Long::sum); + } + } else { + // 未开启轮询 → 直接分配一次总量 + IPatternDetails scaled = PatternScaler.createScaled(processingPattern, totalAmount); + finalCrafts.put(scaled, 1L); + } + } else { + // 有限制 → 拆分 full + remainder + long fullCrafts = totalAmount / perCraftLimit; + long remainder = totalAmount % perCraftLimit; + + if (fullCrafts > 0) { + IPatternDetails scaledFull = PatternScaler.createScaled(processingPattern, perCraftLimit); + finalCrafts.put(scaledFull, fullCrafts); + } + if (remainder > 0) { + IPatternDetails scaledRem = PatternScaler.createScaled(processingPattern, remainder); + finalCrafts.put(scaledRem, 1L); + } + } + } + + crafts.clear(); + crafts.putAll(finalCrafts); + } +} \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeNodeAccessor.java b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeNodeAccessor.java deleted file mode 100644 index 3f4794a..0000000 --- a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeNodeAccessor.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.extendedae_plus.mixin.ae2.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 eap$getWhat(); -} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeNodeMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeNodeMixin.java deleted file mode 100644 index b608c81..0000000 --- a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeNodeMixin.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.extendedae_plus.mixin.ae2.autopattern; - -import appeng.api.stacks.KeyCounter; -import appeng.crafting.CraftingTreeNode; -import appeng.crafting.inv.CraftingSimulationState; -import com.extendedae_plus.util.smartDoubling.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); - } -} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeProcessMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeProcessMixin.java deleted file mode 100644 index da3d706..0000000 --- a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/CraftingTreeProcessMixin.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.extendedae_plus.mixin.ae2.autopattern; - -import appeng.api.crafting.IPatternDetails; -import appeng.api.networking.crafting.ICraftingProvider; -import appeng.api.networking.crafting.ICraftingService; -import appeng.api.stacks.AEKey; -import appeng.crafting.CraftingCalculation; -import appeng.crafting.CraftingTreeNode; -import appeng.crafting.CraftingTreeProcess; -import appeng.crafting.pattern.AEProcessingPattern; -import appeng.me.service.CraftingService; -import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern; -import com.extendedae_plus.api.crafting.ScaledProcessingPattern; -import com.extendedae_plus.config.ModConfigs; -import com.extendedae_plus.util.smartDoubling.PatternScaler; -import com.extendedae_plus.util.smartDoubling.RequestedAmountHolder; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyVariable; - -import java.util.List; -import java.util.stream.StreamSupport; - -import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; - -/** - * 注入 CraftingTreeProcess 构造器尾部:将 AEProcessingPattern 替换为 ScaledProcessingPattern - * 以确保后续执行使用放大后的输入/输出视图。 - */ -@Mixin(CraftingTreeProcess.class) -public abstract class CraftingTreeProcessMixin { - - @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 eap$replaceDetailsAtHead(IPatternDetails original, ICraftingService cc, CraftingCalculation job, IPatternDetails details, CraftingTreeNode craftingTreeNode) { - try { - // 若传入的 details 已经是缩放样板,且原始样板不允许缩放,则直接解包为原始样板 - if (details instanceof ScaledProcessingPattern sp) { - var proc0 = sp.getOriginal(); - if (proc0 instanceof ISmartDoublingAwarePattern aware0 && !aware0.eap$allowScaling()) { - return proc0; - } - } - - if (!(details instanceof AEProcessingPattern proc)) return original; - - // 若样板标记为不允许缩放,则直接跳过 - if (proc instanceof ISmartDoublingAwarePattern aware && !aware.eap$allowScaling()) { - return original; - } - - CraftingTreeNodeAccessor parentAcc = (CraftingTreeNodeAccessor) craftingTreeNode; - AEKey parentTarget = parentAcc.eap$getWhat(); - long requested = RequestedAmountHolder.get(); - RequestedAmountHolder.pop(); - - // 根据配置决定是否在 provider 间轮询分配请求量(默认开启) - long perProvider = 1L; - if (!ModConfigs.PROVIDER_ROUND_ROBIN_ENABLE.get()) { - // 关闭轮询:直接使用完整请求量,不需要查询 provider 列表 - perProvider = requested; - if (perProvider <= 0) perProvider = 1L; - } else { - CraftingService craftingService = (CraftingService) cc; - Iterable providers = craftingService.getProviders(original); - - // 计算 provider 数量;尝试用反射读取内部 providers 列表以避免消费迭代器 - int size; - try { - var cls = providers.getClass(); - var f = cls.getDeclaredField("providers"); // private ArrayList - f.setAccessible(true); - List list = (List) f.get(providers); - size = list == null ? 0 : list.size(); - } catch (Exception ex) { - // 反射失败回退为遍历计数(会消费迭代器) - size = (int) StreamSupport.stream(providers.spliterator(), false).count(); - } - - // 将 requested 在 providers 间均分,向上取整保证每个 provider 分配整数且总量不少于 requested - if (size > 0) { - perProvider = requested / size + ((requested % size) == 0 ? 0 : 1); - if (perProvider <= 0) perProvider = 1L; - } - } - - // 使用每-provider 的分配量来缩放样板 - var scaled = PatternScaler.scale(proc, parentTarget, perProvider); - return scaled != null ? scaled : original; - } catch (Exception e) { - LOGGER.warn("构建倍增样板出错", e); - e.printStackTrace(); - return original; - } - } -} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/PatternProviderLogicContainsRedirectMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/PatternProviderLogicContainsRedirectMixin.java index bb18db1..0a3e911 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/PatternProviderLogicContainsRedirectMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/autopattern/PatternProviderLogicContainsRedirectMixin.java @@ -25,7 +25,7 @@ public class PatternProviderLogicContainsRedirectMixin { try { if (o instanceof ScaledProcessingPattern scaled) { IPatternDetails base = scaled.getOriginal(); - if (base != null && list.indexOf(base) != -1) { + if (list.indexOf(base) != -1) { return true; } } diff --git a/src/main/java/com/extendedae_plus/util/smartDoubling/PatternScaler.java b/src/main/java/com/extendedae_plus/util/smartDoubling/PatternScaler.java index a83d04f..c1aba28 100644 --- a/src/main/java/com/extendedae_plus/util/smartDoubling/PatternScaler.java +++ b/src/main/java/com/extendedae_plus/util/smartDoubling/PatternScaler.java @@ -1,124 +1,106 @@ package com.extendedae_plus.util.smartDoubling; -import appeng.api.crafting.IPatternDetails.IInput; +import appeng.api.crafting.IPatternDetails; +import appeng.api.stacks.AEFluidKey; +import appeng.api.stacks.AEItemKey; import appeng.api.stacks.AEKey; -import appeng.api.stacks.GenericStack; import appeng.crafting.pattern.AEProcessingPattern; import com.extendedae_plus.api.crafting.ScaledProcessingPattern; -import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern; -import com.extendedae_plus.config.ModConfigs; +import com.extendedae_plus.api.crafting.ScaledProcessingPatternAdv; +import net.neoforged.fml.loading.LoadingModList; -import java.util.ArrayList; -import java.util.List; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; public final class PatternScaler { - private PatternScaler() { + private static final boolean advAvailable; + private static final Constructor advCtor; + private static final Class advIfaceClass; + + static { + boolean available = false; + Constructor ctor = null; + Class iface = null; + + try { + // 尝试加载扩展类 + Class clazz = Class.forName("com.extendedae_plus.api.crafting.ScaledProcessingPatternAdv"); + ctor = clazz.getConstructor(IPatternDetails.class, long.class); + + // 加载接口 + iface = Class.forName("net.pedroksl.advanced_ae.common.patterns.IAdvPatternDetails"); + + // 检查是否安装 Advanced AE + if (LoadingModList.get() != null && LoadingModList.get().getModFileById("advanced_ae") != null) { + available = true; + } + } catch (Throwable ignored) { + } + + advAvailable = available; + advCtor = ctor; + advIfaceClass = iface; } - public static ScaledProcessingPattern scale(AEProcessingPattern base, AEKey target, long requestedAmount) { - if (base == null) throw new IllegalArgumentException("base"); - if (target == null) throw new IllegalArgumentException("target"); + private PatternScaler() {} - // 双保险:若样板标记为不允许缩放,直接放弃缩放(返回 null 表示调用方应保持原样板) - if (base instanceof ISmartDoublingAwarePattern aware && !aware.eap$allowScaling()) { - return null; - } - - List baseSparseInputs = base.getSparseInputs(); - List baseSparseOutputs = base.getSparseOutputs(); - IInput[] baseInputs = base.getInputs(); - List baseOutputs = base.getOutputs(); - - // 新逻辑:不再对样板进行单位化处理 - // 找到目标输出在 outputs 中的索引(尝试匹配 target,否则取第一个非空输出) - int targetOutIndex = -1; - for (int i = 0; i < baseOutputs.size(); i++) { - var out = baseOutputs.get(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.size(); i++) { - if (baseOutputs.get(i) != null) { - targetOutIndex = i; - break; + /** + * 创建缩放样板。 + * 自动支持原版 AE 和可选 AAE 的 AdvProcessingPattern。 + */ + public static IPatternDetails createScaled(IPatternDetails base, long multiplier) { + // 尝试 Advanced AE 扩展 + if (advAvailable && advIfaceClass != null && advCtor != null) { + try { + if (advIfaceClass.isInstance(base)) { + return (ScaledProcessingPatternAdv) advCtor.newInstance(base, multiplier); } + } catch (InstantiationException | IllegalAccessException | InvocationTargetException ignored) { + // 出错退回普通逻辑 } } - if (targetOutIndex == -1 && !baseOutputs.isEmpty()) targetOutIndex = 0; - long perOperationTarget = 1L; - if (targetOutIndex >= 0 && baseOutputs.get(targetOutIndex) != null) { - long amt = baseOutputs.get(targetOutIndex).amount(); - if (amt > 0) perOperationTarget = amt; + // 回退原版 + return new ScaledProcessingPattern(base, multiplier); + } + + /** + * 计算基于 limit 的最大允许倍率(单次输出主物品 ≤ limit) + */ + public static int getComputedMul(AEProcessingPattern proc, int limit) { + if (limit <= 0) return 0; // 0 = 不限制 + + long minMul = Long.MAX_VALUE; + + for (IPatternDetails.IInput input : proc.getInputs()) { + long amt = input.getMultiplier(); + if (amt <= 0) continue; + var possible = input.getPossibleInputs(); + if (possible == null || possible.length == 0) continue; + AEKey key = possible[0].what(); + long unitMul = getUnitMultiplier(key); + long limitInUnit = (long) limit * unitMul; + + long allowed = limitInUnit / amt; + allowed = Math.max(1L, allowed); + minMul = Math.min(minMul, allowed); } - // 使用最小整数倍(ceil)策略:直接选择满足请求的最小倍数 - long multiplier = 1L; - if (requestedAmount > 0) { - long needed = requestedAmount / perOperationTarget + ((requestedAmount % perOperationTarget) == 0 ? 0 : 1); - multiplier = needed <= 1L ? 1L : needed; - } - // 应用配置的最大倍数上限(0 表示不限制) + if (minMul == Long.MAX_VALUE) return 0; // 无有效输入 → 不限制 + return minMul > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) minMul; + } + + private static long getUnitMultiplier(AEKey key) { + if (key instanceof AEItemKey) return 1L; + if (key instanceof AEFluidKey) return 1000L; + + // 支持 Mekanism Chemical 等(反射安全) try { - int maxMul = ModConfigs.SMART_SCALING_MAX_MULTIPLIER.get(); - if (maxMul > 0 && multiplier > maxMul) { - multiplier = maxMul; + if ("me.ramidzkh.mekae2.ae2.MekanismKey".equals(key.getClass().getName())) { + return 1000L; } - } catch (Throwable ignore) { - // 配置读取异常时不施加上限 + } catch (Exception ignored) { } - - // 构建压缩输入(将每个输入的 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); - } - - // 构建压缩输出(List) - List scaledCondensedOutputs = new ArrayList<>(baseOutputs.size()); - for (int i = 0; i < baseOutputs.size(); i++) { - GenericStack out = baseOutputs.get(i); - if (out != null) { - scaledCondensedOutputs.add(new GenericStack(out.what(), out.amount() * multiplier)); - } else { - scaledCondensedOutputs.add(null); - } - } - - // 构建稀疏表示(List,直接按 multiplier 放大) - List scaledSparseInputs = new ArrayList<>(baseSparseInputs.size()); - for (int i = 0; i < baseSparseInputs.size(); i++) { - var in = baseSparseInputs.get(i); - if (in != null) { - scaledSparseInputs.add(new GenericStack(in.what(), in.amount() * multiplier)); - } else { - scaledSparseInputs.add(null); - } - } - List scaledSparseOutputs = new ArrayList<>(baseSparseOutputs.size()); - for (int i = 0; i < baseSparseOutputs.size(); i++) { - var out = baseSparseOutputs.get(i); - if (out != null) { - scaledSparseOutputs.add(new GenericStack(out.what(), out.amount() * multiplier)); - } else { - scaledSparseOutputs.add(null); - } - } - - return new ScaledProcessingPattern(base, - base.getDefinition(), - scaledSparseInputs, - scaledSparseOutputs, - scaledInputs, - scaledCondensedOutputs); + return 1L; } } diff --git a/src/main/java/com/extendedae_plus/util/smartDoubling/RequestedAmountHolder.java b/src/main/java/com/extendedae_plus/util/smartDoubling/RequestedAmountHolder.java deleted file mode 100644 index 005f919..0000000 --- a/src/main/java/com/extendedae_plus/util/smartDoubling/RequestedAmountHolder.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.extendedae_plus.util.smartDoubling; - -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 77d884c..c8e4ee7 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -5,6 +5,7 @@ "plugin": "com.extendedae_plus.mixin.ExtendedAEPlusMixinPlugin", "mixins": [ "advancedae.AdvPatternProviderLogicContainsRedirectMixin", + "advancedae.AdvProcessingPatternMixin", "advancedae.accessor.AdvCraftingCPULogicAccessor", "advancedae.accessor.AdvExecutingCraftingJobAccessor", "advancedae.accessor.AdvExecutingCraftingJobTaskProgressAccessor", @@ -13,7 +14,6 @@ "advancedae.compat.PatternProviderLogicVirtualCompletionMixin", "advancedae.helpers.AdvPatternProviderLogicAdvancedMixin", "advancedae.helpers.AdvPatternProviderLogicDoublingMixin", - "advancedae.compat.PatternProviderLogicVirtualCompletionMixin", "advancedae.menu.AdvPatternProviderMenuAdvancedMixin", "ae2.AEProcessingPatternMixin", "ae2.CraftingCalculationMixin", @@ -28,10 +28,10 @@ "ae2.accessor.PatternProviderLogicPatternInputsAccessor", "ae2.accessor.PatternProviderLogicPatternsAccessor", "ae2.accessor.PatternProviderMenuAdvancedAccessor", + "ae2.autopattern.CraftingCalculationMixin", "ae2.autopattern.CraftingServiceGetProvidersMixin", - "ae2.autopattern.CraftingTreeNodeAccessor", - "ae2.autopattern.CraftingTreeNodeMixin", - "ae2.autopattern.CraftingTreeProcessMixin", + "ae2.autopattern.CraftingSimulationStateAccessor", + "ae2.autopattern.CraftingSimulationStateMixin", "ae2.autopattern.PatternProviderLogicContainsRedirectMixin", "ae2.compat.PatternProviderLogicCompatMixin", "ae2.helpers.InterfaceLogicChannelCardMixin",