新版智能倍增;添加智能倍增对高级处理样板支持

This commit is contained in:
C-H716 2025-11-25 03:23:38 +08:00
parent 0a820c734b
commit e478bf7e35
17 changed files with 521 additions and 376 deletions

View File

@ -7,10 +7,10 @@ import appeng.api.stacks.GenericStack;
import appeng.api.stacks.KeyCounter; import appeng.api.stacks.KeyCounter;
import appeng.crafting.pattern.AEProcessingPattern; import appeng.crafting.pattern.AEProcessingPattern;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -18,126 +18,163 @@ import java.util.Objects;
* 缩放后的处理样板结构完全模拟 AEProcessingPattern * 缩放后的处理样板结构完全模拟 AEProcessingPattern
* 保持 sparse/condensed/inputs 的一致性同时保存原始样板 * 保持 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; // 原始样板引用 public ScaledProcessingPattern(@NotNull IPatternDetails original, long multiplier) {
private final AEItemKey definition; // 样板物品 if (multiplier <= 0) throw new IllegalArgumentException("multiplier must be > 0");
private final List<GenericStack> sparseInputs; // 缩放后的稀疏输入List 以适配 1.21 API this.original = original;
private final List<GenericStack> sparseOutputs; // 缩放后的稀疏输出List 以适配 1.21 API this.multiplier = multiplier;
private final IInput[] inputs; // 缩放后的压缩输入
private final List<GenericStack> condensedOutputs; // 缩放后的压缩输出List 以适配 1.21 API
public ScaledProcessingPattern(
AEProcessingPattern original,
AEItemKey definition,
List<GenericStack> sparseInputs,
List<GenericStack> sparseOutputs,
IInput[] inputs,
List<GenericStack> 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)));
} }
/* -------------------- API 实现 -------------------- */ public @NotNull IPatternDetails getOriginal() {return this.original;}
public AEProcessingPattern getOriginal() {
return this.original;
}
@Override @Override
public AEItemKey getDefinition() { public AEItemKey getDefinition() {
return this.definition; return this.original.getDefinition();
} }
@Override @Override
public IInput[] getInputs() { public IInput[] getInputs() {
return this.inputs; IInput[] original = this.original.getInputs();
} IInput[] scaled = new IInput[original.length];
for (int i = 0; i < original.length; i++) {
@Override scaled[i] = new ScaledInput(original[i], this.multiplier);
public GenericStack getPrimaryOutput() { }
if (!this.condensedOutputs.isEmpty()) return this.condensedOutputs.get(0); return scaled;
return this.original.getPrimaryOutput();
} }
@Override @Override
public List<GenericStack> getOutputs() { public List<GenericStack> getOutputs() {
return this.condensedOutputs; var original = this.original.getOutputs();
List<GenericStack> 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 @Override
public boolean supportsPushInputsToExternalInventory() { public void pushInputsToExternalInventory(KeyCounter[] inputHolder, PatternInputSink sink) {
return this.original.supportsPushInputsToExternalInventory(); // 如果 sparseInputs inputs 一一对应则无需 reorder
} if (((AEProcessingPattern) this.original).getSparseInputs().size() == this.original.getInputs().length) {
// AEProcessingPattern 的默认逻辑
IPatternDetails.super.pushInputsToExternalInventory(inputHolder, sink);
return;
}
@Override // 否则必须按 sparse 输入顺序推送
public void pushInputsToExternalInventory(KeyCounter[] inputHolder, PatternInputSink inputSink) { var allInputs = new KeyCounter();
// 保持和 AEProcessingPattern 一致 sparseInputs 驱动 for (var ctr : inputHolder) {
if (this.sparseInputs.size() == this.inputs.length) { allInputs.addAll(ctr);
IPatternDetails.super.pushInputsToExternalInventory(inputHolder, inputSink); }
} else {
KeyCounter allInputs = new KeyCounter(); var sparse = this.getSparseInputs(); // 使用已缩放倍率的顺序表
for (KeyCounter counter : inputHolder) {
allInputs.addAll(counter); for (var sparseInput : sparse) {
} if (sparseInput == null) continue;
for (GenericStack sparseInput : this.sparseInputs) {
if (sparseInput != null) { var key = sparseInput.what();
AEKey key = sparseInput.what(); long amount = sparseInput.amount();
long amount = sparseInput.amount();
long available = allInputs.get(key); long available = allInputs.get(key);
if (available < amount) { if (available < amount) {
throw new RuntimeException("Expected at least %d of %s when pushing scaled pattern, but only %d available" throw new IllegalStateException(
.formatted(amount, key, available)); "Expected " + amount + " of " + key + " but only " + available + " available"
} );
inputSink.pushInput(key, amount);
allInputs.remove(key, amount);
}
} }
sink.pushInput(key, amount);
allInputs.remove(key, amount);
} }
} }
public List<GenericStack> getSparseInputs() { protected List<GenericStack> getSparseInputs() {
return this.sparseInputs; var original = ((AEProcessingPattern) this.original).getSparseInputs();
List<GenericStack> 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<GenericStack> getSparseOutputs() { public List<GenericStack> getSparseOutputs() {
return this.sparseOutputs; var original = ((AEProcessingPattern) this.original).getSparseOutputs();
List<GenericStack> 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 { @Override
private final GenericStack[] template; public boolean equals(Object obj) {
private final long multiplier; 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) { @Override
this.template = template; public String toString() {
this.multiplier = multiplier; 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 @Override
public GenericStack[] getPossibleInputs() { public long getMultiplier() {return this.original.getMultiplier() * this.multiplier;}
return this.template;
}
@Override @Override
public long getMultiplier() { public boolean isValid(AEKey input, Level level) {return this.original.isValid(input, level);}
return this.multiplier;
}
@Override @Override
public boolean isValid(AEKey input, Level level) { public @Nullable AEKey getRemainingKey(AEKey template) {return this.original.getRemainingKey(template);}
return input.matches(this.template[0]);
}
@Override
public @Nullable AEKey getRemainingKey(AEKey template) {
return null;
}
} }
} }

View File

@ -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<AEKey, Direction> 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<GenericStack> getSparseInputs() {
var original = ((AdvProcessingPattern) this.original).getSparseInputs();
List<GenericStack> 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<GenericStack> getSparseOutputs() {
var original = ((AdvProcessingPattern) this.original).getSparseOutputs();
List<GenericStack> 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<AEKey, Direction> getDirectionMap() {
return this.dirMap;
}
@Override
public Direction getDirectionSideForInputKey(AEKey key) {
return this.dirMap.get(key);
}
}

View File

@ -3,4 +3,9 @@ package com.extendedae_plus.api.smartDoubling;
public interface ISmartDoublingAwarePattern { public interface ISmartDoublingAwarePattern {
boolean eap$allowScaling(); boolean eap$allowScaling();
void eap$setAllowScaling(boolean allow); void eap$setAllowScaling(boolean allow);
// 翻倍限制0 表示无限制
int eap$getMultiplierLimit();
void eap$setMultiplierLimit(int limit);
} }

View File

@ -25,7 +25,7 @@ public class AdvPatternProviderLogicContainsRedirectMixin {
try { try {
if (o instanceof ScaledProcessingPattern scaled) { if (o instanceof ScaledProcessingPattern scaled) {
IPatternDetails base = scaled.getOriginal(); IPatternDetails base = scaled.getOriginal();
if (base != null && list.indexOf(base) != -1) { if (list.indexOf(base) != -1) {
return true; return true;
} }
} }

View File

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

View File

@ -1,7 +1,6 @@
package com.extendedae_plus.mixin.advancedae.helpers; package com.extendedae_plus.mixin.advancedae.helpers;
import appeng.api.crafting.IPatternDetails; import appeng.api.crafting.IPatternDetails;
import appeng.crafting.pattern.AEProcessingPattern;
import com.extendedae_plus.api.smartDoubling.ISmartDoubling; import com.extendedae_plus.api.smartDoubling.ISmartDoubling;
import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern; import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern;
import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderLogicPatternsAccessor; import com.extendedae_plus.mixin.advancedae.accessor.AdvPatternProviderLogicPatternsAccessor;
@ -35,7 +34,7 @@ public class AdvPatternProviderLogicDoublingMixin implements ISmartDoubling {
try { try {
var list = ((AdvPatternProviderLogicPatternsAccessor) this).eap$patterns(); var list = ((AdvPatternProviderLogicPatternsAccessor) this).eap$patterns();
for (IPatternDetails details : list) { for (IPatternDetails details : list) {
if (details instanceof AEProcessingPattern proc && proc instanceof ISmartDoublingAwarePattern aware) { if (details instanceof ISmartDoublingAwarePattern aware) {
aware.eap$setAllowScaling(value); aware.eap$setAllowScaling(value);
} }
} }
@ -63,7 +62,7 @@ public class AdvPatternProviderLogicDoublingMixin implements ISmartDoubling {
var list = ((AdvPatternProviderLogicPatternsAccessor) this).eap$patterns(); var list = ((AdvPatternProviderLogicPatternsAccessor) this).eap$patterns();
boolean allow = this.eap$smartDoubling; boolean allow = this.eap$smartDoubling;
for (IPatternDetails details : list) { for (IPatternDetails details : list) {
if (details instanceof AEProcessingPattern proc && proc instanceof ISmartDoublingAwarePattern aware) { if (details instanceof ISmartDoublingAwarePattern aware) {
aware.eap$setAllowScaling(allow); aware.eap$setAllowScaling(allow);
} }
} }

View File

@ -9,6 +9,8 @@ import org.spongepowered.asm.mixin.Unique;
public class AEProcessingPatternMixin implements ISmartDoublingAwarePattern { public class AEProcessingPatternMixin implements ISmartDoublingAwarePattern {
@Unique @Unique
private boolean eap$allowScaling = false; // 默认不允许缩放 private boolean eap$allowScaling = false; // 默认不允许缩放
@Unique
private int eap$multiplierLimit = 0; // 模式级别的倍数上限0 表示不限制
@Override @Override
public boolean eap$allowScaling() { public boolean eap$allowScaling() {
@ -19,4 +21,14 @@ public class AEProcessingPatternMixin implements ISmartDoublingAwarePattern {
public void eap$setAllowScaling(boolean allow) { public void eap$setAllowScaling(boolean allow) {
this.eap$allowScaling = 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);
}
} }

View File

@ -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 = "<init>",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;
}
}

View File

@ -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<IPatternDetails, Long> getCrafts();
}

View File

@ -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<CraftingPlan> cir) {
CraftingSimulationStateAccessor accessor = (CraftingSimulationStateAccessor) state;
Map<IPatternDetails, Long> crafts = accessor.getCrafts();
// 存放最终分配后的 crafts
Map<IPatternDetails, Long> finalCrafts = new LinkedHashMap<>();
for (Map.Entry<IPatternDetails, Long> 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);
}
}

View File

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

View File

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

View File

@ -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 = "<init>(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<ICraftingProvider> providers = craftingService.getProviders(original);
// 计算 provider 数量尝试用反射读取内部 providers 列表以避免消费迭代器
int size;
try {
var cls = providers.getClass();
var f = cls.getDeclaredField("providers"); // private ArrayList<ICraftingProvider>
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;
}
}
}

View File

@ -25,7 +25,7 @@ public class PatternProviderLogicContainsRedirectMixin {
try { try {
if (o instanceof ScaledProcessingPattern scaled) { if (o instanceof ScaledProcessingPattern scaled) {
IPatternDetails base = scaled.getOriginal(); IPatternDetails base = scaled.getOriginal();
if (base != null && list.indexOf(base) != -1) { if (list.indexOf(base) != -1) {
return true; return true;
} }
} }

View File

@ -1,124 +1,106 @@
package com.extendedae_plus.util.smartDoubling; 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.AEKey;
import appeng.api.stacks.GenericStack;
import appeng.crafting.pattern.AEProcessingPattern; import appeng.crafting.pattern.AEProcessingPattern;
import com.extendedae_plus.api.crafting.ScaledProcessingPattern; import com.extendedae_plus.api.crafting.ScaledProcessingPattern;
import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern; import com.extendedae_plus.api.crafting.ScaledProcessingPatternAdv;
import com.extendedae_plus.config.ModConfigs; import net.neoforged.fml.loading.LoadingModList;
import java.util.ArrayList; import java.lang.reflect.Constructor;
import java.util.List; import java.lang.reflect.InvocationTargetException;
public final class PatternScaler { 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) { private PatternScaler() {}
if (base == null) throw new IllegalArgumentException("base");
if (target == null) throw new IllegalArgumentException("target");
// 双保险若样板标记为不允许缩放直接放弃缩放返回 null 表示调用方应保持原样板 /**
if (base instanceof ISmartDoublingAwarePattern aware && !aware.eap$allowScaling()) { * 创建缩放样板
return null; * 自动支持原版 AE 和可选 AAE AdvProcessingPattern
} */
public static IPatternDetails createScaled(IPatternDetails base, long multiplier) {
List<GenericStack> baseSparseInputs = base.getSparseInputs(); // 尝试 Advanced AE 扩展
List<GenericStack> baseSparseOutputs = base.getSparseOutputs(); if (advAvailable && advIfaceClass != null && advCtor != null) {
IInput[] baseInputs = base.getInputs(); try {
List<GenericStack> baseOutputs = base.getOutputs(); if (advIfaceClass.isInstance(base)) {
return (ScaledProcessingPatternAdv) advCtor.newInstance(base, multiplier);
// 新逻辑不再对样板进行单位化处理
// 找到目标输出在 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;
} }
} catch (InstantiationException | IllegalAccessException | InvocationTargetException ignored) {
// 出错退回普通逻辑
} }
} }
if (targetOutIndex == -1 && !baseOutputs.isEmpty()) targetOutIndex = 0;
long perOperationTarget = 1L; // 回退原版
if (targetOutIndex >= 0 && baseOutputs.get(targetOutIndex) != null) { return new ScaledProcessingPattern(base, multiplier);
long amt = baseOutputs.get(targetOutIndex).amount(); }
if (amt > 0) perOperationTarget = amt;
/**
* 计算基于 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策略直接选择满足请求的最小倍数 if (minMul == Long.MAX_VALUE) return 0; // 无有效输入 不限制
long multiplier = 1L; return minMul > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) minMul;
if (requestedAmount > 0) { }
long needed = requestedAmount / perOperationTarget + ((requestedAmount % perOperationTarget) == 0 ? 0 : 1);
multiplier = needed <= 1L ? 1L : needed; private static long getUnitMultiplier(AEKey key) {
} if (key instanceof AEItemKey) return 1L;
// 应用配置的最大倍数上限0 表示不限制 if (key instanceof AEFluidKey) return 1000L;
// 支持 Mekanism Chemical 反射安全
try { try {
int maxMul = ModConfigs.SMART_SCALING_MAX_MULTIPLIER.get(); if ("me.ramidzkh.mekae2.ae2.MekanismKey".equals(key.getClass().getName())) {
if (maxMul > 0 && multiplier > maxMul) { return 1000L;
multiplier = maxMul;
} }
} catch (Throwable ignore) { } catch (Exception ignored) {
// 配置读取异常时不施加上限
} }
return 1L;
// 构建压缩输入将每个输入的 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<GenericStack> 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<GenericStack> 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<GenericStack> 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);
} }
} }

View File

@ -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<Deque<Long>> HOLDER = ThreadLocal.withInitial(ArrayDeque::new);
private RequestedAmountHolder() {
}
/**
* Push a requested amount onto the thread-local stack.
*/
public static void push(long v) {
Deque<Long> dq = HOLDER.get();
dq.push(v);
}
/**
* Pop the top value from the thread-local stack. Safe if empty.
*/
public static void pop() {
Deque<Long> dq = HOLDER.get();
if (dq.isEmpty()) {
return;
}
dq.pop();
}
/**
* Peek the current requested amount or return 0 if none.
*/
public static long get() {
Deque<Long> dq = HOLDER.get();
Long v = dq.peek();
return v == null ? 0L : v;
}
}

View File

@ -5,6 +5,7 @@
"plugin": "com.extendedae_plus.mixin.ExtendedAEPlusMixinPlugin", "plugin": "com.extendedae_plus.mixin.ExtendedAEPlusMixinPlugin",
"mixins": [ "mixins": [
"advancedae.AdvPatternProviderLogicContainsRedirectMixin", "advancedae.AdvPatternProviderLogicContainsRedirectMixin",
"advancedae.AdvProcessingPatternMixin",
"advancedae.accessor.AdvCraftingCPULogicAccessor", "advancedae.accessor.AdvCraftingCPULogicAccessor",
"advancedae.accessor.AdvExecutingCraftingJobAccessor", "advancedae.accessor.AdvExecutingCraftingJobAccessor",
"advancedae.accessor.AdvExecutingCraftingJobTaskProgressAccessor", "advancedae.accessor.AdvExecutingCraftingJobTaskProgressAccessor",
@ -13,7 +14,6 @@
"advancedae.compat.PatternProviderLogicVirtualCompletionMixin", "advancedae.compat.PatternProviderLogicVirtualCompletionMixin",
"advancedae.helpers.AdvPatternProviderLogicAdvancedMixin", "advancedae.helpers.AdvPatternProviderLogicAdvancedMixin",
"advancedae.helpers.AdvPatternProviderLogicDoublingMixin", "advancedae.helpers.AdvPatternProviderLogicDoublingMixin",
"advancedae.compat.PatternProviderLogicVirtualCompletionMixin",
"advancedae.menu.AdvPatternProviderMenuAdvancedMixin", "advancedae.menu.AdvPatternProviderMenuAdvancedMixin",
"ae2.AEProcessingPatternMixin", "ae2.AEProcessingPatternMixin",
"ae2.CraftingCalculationMixin", "ae2.CraftingCalculationMixin",
@ -28,10 +28,10 @@
"ae2.accessor.PatternProviderLogicPatternInputsAccessor", "ae2.accessor.PatternProviderLogicPatternInputsAccessor",
"ae2.accessor.PatternProviderLogicPatternsAccessor", "ae2.accessor.PatternProviderLogicPatternsAccessor",
"ae2.accessor.PatternProviderMenuAdvancedAccessor", "ae2.accessor.PatternProviderMenuAdvancedAccessor",
"ae2.autopattern.CraftingCalculationMixin",
"ae2.autopattern.CraftingServiceGetProvidersMixin", "ae2.autopattern.CraftingServiceGetProvidersMixin",
"ae2.autopattern.CraftingTreeNodeAccessor", "ae2.autopattern.CraftingSimulationStateAccessor",
"ae2.autopattern.CraftingTreeNodeMixin", "ae2.autopattern.CraftingSimulationStateMixin",
"ae2.autopattern.CraftingTreeProcessMixin",
"ae2.autopattern.PatternProviderLogicContainsRedirectMixin", "ae2.autopattern.PatternProviderLogicContainsRedirectMixin",
"ae2.compat.PatternProviderLogicCompatMixin", "ae2.compat.PatternProviderLogicCompatMixin",
"ae2.helpers.InterfaceLogicChannelCardMixin", "ae2.helpers.InterfaceLogicChannelCardMixin",