新版缩放

This commit is contained in:
C-H716 2025-11-02 17:41:35 +08:00
parent af0d9e25dc
commit c02d0eb263
5 changed files with 227 additions and 345 deletions

View File

@ -4,200 +4,107 @@ 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 class ScaledProcessingPattern implements IPatternDetails {
// 最小化实例字段只保留原始样板引用定义和倍数
private final AEProcessingPattern original; // 原始样板引用
private final AEItemKey definition; // 样板物品直接委托自 original
private final long multiplier; // 乘数外部可视为视图参数
private final AEProcessingPattern original;
private final long multiplier;
// 延迟计算缓存轻量化实例时避免在构造器中分配大数组
private transient volatile IInput[] inputsCache;
private transient volatile GenericStack[] outputsCache;
private transient volatile GenericStack[] sparseInputsCache;
private transient volatile GenericStack[] sparseOutputsCache;
public ScaledProcessingPattern(AEProcessingPattern original, AEItemKey definition, long multiplier) {
this.original = Objects.requireNonNull(original);
this.definition = Objects.requireNonNull(definition);
this.multiplier = multiplier <= 0 ? 1L : multiplier;
public ScaledProcessingPattern(AEProcessingPattern original, long multiplier) {
if (original == null) throw new IllegalArgumentException("original cannot be null");
if (multiplier <= 0) throw new IllegalArgumentException("multiplier must be > 0");
this.original = original;
this.multiplier = multiplier;
}
/* -------------------- API 实现 -------------------- */
public AEProcessingPattern getOriginal() {
return original;
}
public AEProcessingPattern getOriginal() { return original; }
public long getMultiplier() { return multiplier; }
@Override
public AEItemKey getDefinition() {
return definition;
return original.getDefinition();
}
@Override
public IInput[] getInputs() {
IInput[] cached = this.inputsCache;
if (cached == null) {
synchronized (this) {
cached = this.inputsCache;
if (cached == null) {
var base = original.getInputs();
IInput[] arr = new IInput[base.length];
for (int i = 0; i < base.length; i++) {
var in = base[i];
// 不复制 template 数组直接复用原始的 possible inputs仅放大 multiplier
arr[i] = new Input(in.getPossibleInputs(), in.getMultiplier() * this.multiplier);
}
this.inputsCache = arr;
cached = arr;
}
}
IPatternDetails.IInput[] orig = original.getInputs();
IInput[] scaled = new IInput[orig.length];
for (int i = 0; i < orig.length; i++) {
scaled[i] = new ScaledInput(orig[i], multiplier);
}
return cached;
return scaled;
}
@Override
public GenericStack[] getOutputs() {
GenericStack[] cached = this.outputsCache;
if (cached == null) {
synchronized (this) {
cached = this.outputsCache;
if (cached == null) {
var baseOutputs = original.getOutputs();
GenericStack[] arr = new GenericStack[baseOutputs.length];
for (int i = 0; i < baseOutputs.length; i++) {
var o = baseOutputs[i];
if (o != null) arr[i] = new GenericStack(o.what(), o.amount() * this.multiplier);
}
this.outputsCache = arr;
cached = arr;
}
GenericStack[] orig = original.getOutputs();
GenericStack[] scaled = new GenericStack[orig.length];
for (int i = 0; i < orig.length; i++) {
if (orig[i] != null) {
scaled[i] = new GenericStack(orig[i].what(), orig[i].amount() * multiplier);
}
}
return cached;
return scaled;
}
// 兼容性方法
public GenericStack[] getSparseInputs() {
GenericStack[] cached = this.sparseInputsCache;
if (cached == null) {
synchronized (this) {
cached = this.sparseInputsCache;
if (cached == null) {
var base = original.getSparseInputs();
GenericStack[] arr = new GenericStack[base.length];
for (int i = 0; i < base.length; i++) {
var v = base[i];
if (v != null) arr[i] = new GenericStack(v.what(), v.amount() * this.multiplier);
}
this.sparseInputsCache = arr;
cached = arr;
}
GenericStack[] orig = original.getSparseInputs();
GenericStack[] scaled = new GenericStack[orig.length];
for (int i = 0; i < orig.length; i++) {
if (orig[i] != null) {
scaled[i] = new GenericStack(orig[i].what(), orig[i].amount() * multiplier);
}
}
return cached;
return scaled;
}
public GenericStack[] getSparseOutputs() {
GenericStack[] cached = this.sparseOutputsCache;
if (cached == null) {
synchronized (this) {
cached = this.sparseOutputsCache;
if (cached == null) {
var base = original.getSparseOutputs();
GenericStack[] arr = new GenericStack[base.length];
for (int i = 0; i < base.length; i++) {
var v = base[i];
if (v != null) arr[i] = new GenericStack(v.what(), v.amount() * this.multiplier);
}
this.sparseOutputsCache = arr;
cached = arr;
}
GenericStack[] orig = original.getSparseOutputs();
GenericStack[] scaled = new GenericStack[orig.length];
for (int i = 0; i < orig.length; i++) {
if (orig[i] != null) {
scaled[i] = new GenericStack(orig[i].what(), orig[i].amount() * multiplier);
}
}
return cached;
return scaled;
}
// equals / hashCode 必须包含 multiplier不同倍率 = 不同 key
@Override
public int hashCode() {
int h = original.hashCode();
h = 31 * h + Long.hashCode(multiplier);
return h;
}
@Override
public GenericStack getPrimaryOutput() {
var outs = getOutputs();
if (outs.length > 0 && outs[0] != null) return outs[0];
return original.getPrimaryOutput();
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;
}
@Override
public boolean supportsPushInputsToExternalInventory() {
return original.supportsPushInputsToExternalInventory();
public String toString() {
return "Scaled[" + original.getDefinition().getItem() + " × " + multiplier + "]";
}
@Override
public void pushInputsToExternalInventory(KeyCounter[] inputHolder, PatternInputSink inputSink) {
// 使用 lazy 计算的 sparseInputs inputs 来驱动当两者长度一致时直接委托
GenericStack[] sInputs = getSparseInputs();
IInput[] ins = getInputs();
if (sInputs.length == ins.length) {
IPatternDetails.super.pushInputsToExternalInventory(inputHolder, inputSink);
return;
private static class ScaledInput implements IInput {
private final IInput delegate;
private final long mul;
private ScaledInput(IInput delegate, long mul) {
this.delegate = delegate;
this.mul = mul;
}
KeyCounter allInputs = new KeyCounter();
for (KeyCounter counter : inputHolder) {
allInputs.addAll(counter);
}
for (GenericStack sparseInput : sInputs) {
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);
}
}
@Override public GenericStack[] getPossibleInputs() { return delegate.getPossibleInputs(); }
@Override public long getMultiplier() { return delegate.getMultiplier() * mul; }
@Override public boolean isValid(AEKey input, Level level) { return delegate.isValid(input, level); }
@Override public @Nullable AEKey getRemainingKey(AEKey template) { return delegate.getRemainingKey(template); }
}
/* -------------------- 缩放输入代理 -------------------- */
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;
}
@Override
public GenericStack[] getPossibleInputs() {
return this.template;
}
@Override
public long getMultiplier() {
return this.multiplier;
}
@Override
public boolean isValid(AEKey input, Level level) {
return input.matches(this.template[0]);
}
@Override
public @Nullable AEKey getRemainingKey(AEKey template) {
return null;
}
}
}
}

View File

@ -1,8 +1,6 @@
package com.extendedae_plus.ae.api.crafting;
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.core.Direction;
@ -18,8 +16,8 @@ public final class ScaledProcessingPatternAdv extends ScaledProcessingPattern im
private final AdvPatternDetails adv;
public ScaledProcessingPatternAdv(AEProcessingPattern original, AEItemKey definition, long multiplier) {
super(original, definition, multiplier);
public ScaledProcessingPatternAdv(AEProcessingPattern original, long multiplier) {
super(original, multiplier);
this.adv = (AdvPatternDetails) original;
}
@ -40,30 +38,30 @@ public final class ScaledProcessingPatternAdv extends ScaledProcessingPattern im
@Override
public void pushInputsToExternalInventory(KeyCounter[] inputHolder, PatternInputSink inputSink) {
// 使用 lazy 计算的 sparseInputs inputs 来驱动当两者长度一致时直接委托
GenericStack[] sInputs = getSparseInputs();
IInput[] ins = getInputs();
if (sInputs.length == ins.length) {
super.pushInputsToExternalInventory(inputHolder, inputSink);
return;
}
KeyCounter allInputs = new KeyCounter();
for (KeyCounter counter : inputHolder) {
allInputs.addAll(counter);
}
for (GenericStack sparseInput : sInputs) {
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);
}
}
// // 使用 lazy 计算的 sparseInputs inputs 来驱动当两者长度一致时直接委托
// GenericStack[] sInputs = getSparseInputs();
// IInput[] ins = getInputs();
// if (sInputs.length == ins.length) {
// super.pushInputsToExternalInventory(inputHolder, inputSink);
// return;
// }
//
// KeyCounter allInputs = new KeyCounter();
// for (KeyCounter counter : inputHolder) {
// allInputs.addAll(counter);
// }
// for (GenericStack sparseInput : sInputs) {
// 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);
// }
// }
}
}

View File

@ -0,0 +1,103 @@
package com.extendedae_plus.mixin.ae2.autopattern;
import appeng.api.crafting.IPatternDetails;
import appeng.crafting.inv.CraftingSimulationState;
import appeng.crafting.pattern.AEProcessingPattern;
import com.extendedae_plus.ae.api.crafting.ScaledProcessingPattern;
import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern;
import com.extendedae_plus.util.smartDoubling.PatternScaler;
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.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.IdentityHashMap;
import java.util.Map;
@Mixin(value = CraftingSimulationState.class, remap = false)
public abstract class CraftingSimulationStateMixin {
@Shadow private Map<IPatternDetails, Long> crafts;
/** 仅用于无限制缩放时的合并缓存 */
private final Map<AEProcessingPattern, ScaledProcessingPattern> scaledCache = new IdentityHashMap<>();
@Inject(method = "addCrafting", at = @At("HEAD"), cancellable = true)
private void onAddCrafting(IPatternDetails details, long craftsAmount, CallbackInfo ci) {
ci.cancel();
if (craftsAmount <= 0 || details == null) return;
if (details instanceof AEProcessingPattern processingPattern) {
boolean allowScaling = true;
int perCraftLimit = 0;
if (processingPattern instanceof ISmartDoublingAwarePattern aware) {
allowScaling = aware.eap$allowScaling();
perCraftLimit = aware.eap$getMultiplierLimit();
}
if (!allowScaling) {
crafts.merge(processingPattern, craftsAmount, Long::sum);
return;
}
// === 需求为 1 直接用原样板 ===
if (craftsAmount == 1) {
crafts.merge(processingPattern, 1L, Long::sum);
return;
}
// === 计算实际限制 ===
if (perCraftLimit > 0) {
perCraftLimit = Math.min(perCraftLimit, PatternScaler.getComputedMul(processingPattern, perCraftLimit));
}
if (perCraftLimit <= 0) {
// 无限制 合并缩放
mergeUnlimited(processingPattern, craftsAmount);
} else {
// 有限制 拆分
splitLimited(processingPattern, craftsAmount, perCraftLimit);
}
} else {
crafts.merge(details, craftsAmount, Long::sum);
}
}
/** 无限制:合并倍率 */
private void mergeUnlimited(AEProcessingPattern original, long multiplier) {
ScaledProcessingPattern existing = scaledCache.get(original);
long total = multiplier;
if (existing != null) {
total += existing.getMultiplier();
crafts.remove(existing);
}
IPatternDetails scaled = PatternScaler.createScaled(original, total);
if (scaled instanceof ScaledProcessingPattern sp) {
scaledCache.put(original, sp);
crafts.put(sp, 1L);
} else {
crafts.put(original, total); // 退化为原样板
}
}
/** 有限制:拆分 full + remainder */
private void splitLimited(AEProcessingPattern original, long totalAmount, int limit) {
long fullCrafts = totalAmount / limit;
long remainder = totalAmount % limit;
if (fullCrafts > 0) {
IPatternDetails scaled = PatternScaler.createScaled(original, limit);
crafts.merge(scaled, fullCrafts, Long::sum);
}
if (remainder > 0) {
IPatternDetails scaled = PatternScaler.createScaled(original, remainder);
crafts.merge(scaled, 1L, Long::sum);
}
}
}

View File

@ -1,184 +1,60 @@
package com.extendedae_plus.util.smartDoubling;
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.ae.api.crafting.ScaledProcessingPattern;
import com.extendedae_plus.api.smartDoubling.ISmartDoublingAwarePattern;
import com.extendedae_plus.config.ModConfig;
import net.minecraftforge.fml.loading.LoadingModList;
import java.lang.reflect.Constructor;
public final class PatternScaler {
// ---------- 静态缓存反射 ----------
private static final boolean advAvailable;
private static final Constructor<?> advCtor;
private static final Class<?> advIfaceClass;
private PatternScaler() {}
static {
boolean available = false;
Constructor<?> ctor = null;
Class<?> iface = null;
try {
// 尝试加载扩展类
Class<?> clazz = Class.forName("com.extendedae_plus.ae.api.crafting.ScaledProcessingPatternAdv");
ctor = clazz.getConstructor(AEProcessingPattern.class, AEItemKey.class, long.class);
// 加载接口
iface = Class.forName("net.pedroksl.advanced_ae.common.patterns.AdvPatternDetails");
// 检查是否安装 Advanced AE
if (LoadingModList.get() != null && LoadingModList.get().getModFileById("advanced_ae") != null) {
available = true;
}
} catch (Throwable ignored) {
/**
* 创建缩放样板
* <p>如果 multiplier 1直接返回原始样板避免无意义包装</p>
*/
public static IPatternDetails createScaled(AEProcessingPattern original, long multiplier) {
if (multiplier <= 1) {
return original; // 关键demand=1 limit=1 直接用原样板
}
advAvailable = available;
advCtor = ctor;
advIfaceClass = iface;
}
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");
// 双保险若样板标记为不允许缩放直接放弃缩放返回 null 表示调用方应保持原样板
if (base instanceof ISmartDoublingAwarePattern aware && !aware.eap$allowScaling()) {
return null;
}
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;
}
long multiplier = 1L; // 默认倍数
// ---------------------- 优先模式限制 ----------------------
boolean patternHasLimit = false;
if (base instanceof ISmartDoublingAwarePattern aware) {
int patternMulLimit = aware.eap$getMultiplierLimit();
if (patternMulLimit > 0) {
multiplier = patternMulLimit; // 直接使用模式限制作为倍数
patternHasLimit = true;
}
}
// ---------------------- 全局逻辑仅在没有模式限制时生效 ----------------------
if (!patternHasLimit && requestedAmount > 0) {
// 计算满足请求量的最小倍数
long needed = requestedAmount / perOperationTarget + ((requestedAmount % perOperationTarget) == 0 ? 0 : 1);
multiplier = Math.max(needed, 1L);
// 应用全局上限
int maxMul = ModConfig.INSTANCE.smartScalingMaxMultiplier;
if (maxMul > 0 && multiplier > maxMul) multiplier = maxMul;
}
// ---------------------- 小请求绕过 ----------------------
try {
int minBenefit = ModConfig.INSTANCE.smartScalingMinBenefitFactor;
if (minBenefit > 1 && requestedAmount > 0 && requestedAmount < perOperationTarget * (long) minBenefit) {
return null;
}
} catch (Throwable ignore) {}
// ---------- Advanced AE 扩展 ----------
if (advAvailable && advIfaceClass != null && advCtor != null) {
try {
if (advIfaceClass.isInstance(base)) {
return (ScaledProcessingPattern) advCtor.newInstance(base, base.getDefinition(), multiplier);
}
} catch (Throwable ignore) {
// 出错就退回普通逻辑
}
}
// 仅使用 multiplier 构建轻量化 ScaledProcessingPattern具体视图按需计算
return new ScaledProcessingPattern(base, base.getDefinition(), multiplier);
}
public static int getComputedMul(AEProcessingPattern proc, int limit) {
int computedMul = 0; // 默认 0 表示不限制
if (limit > 0) {
long minMul = Long.MAX_VALUE;
for (var input : proc.getInputs()) {
long amt = input.getMultiplier();
if (amt <= 0) continue;
AEKey key = input.getPossibleInputs()[0].what();
// 使用统一单位换算
long unitMultiplier = getUnitMultiplier(key);
long limitInAEUnit = limit * unitMultiplier;
// 计算该输入允许的倍数
long allowedMul = limitInAEUnit / amt;
// 保证至少为 1
allowedMul = Math.max(1, allowedMul);
// 取最小值保证所有输入都不超过 limit
minMul = Math.min(minMul, allowedMul);
}
if (minMul != Long.MAX_VALUE) {
computedMul = (int) Math.min(minMul, Integer.MAX_VALUE);
}
}
return computedMul;
return new ScaledProcessingPattern(original, multiplier);
}
/**
* 获取 AEKey 的单位换算系数
* 物品默认 1流体默认 1000其它类型可通过扩展接口提供
* 计算基于 limit 的最大允许倍率单次输出主物品 limit
*/
public static int getComputedMul(AEProcessingPattern proc, int limit) {
if (limit <= 0) return 0; // 0 = 不限制
long minMul = Long.MAX_VALUE;
for (var input : proc.getInputs()) {
long amt = input.getMultiplier();
if (amt <= 0) continue;
AEKey key = input.getPossibleInputs()[0].what();
long unitMultiplier = getUnitMultiplier(key);
long limitInAEUnit = (long) limit * unitMultiplier;
long allowedMul = limitInAEUnit / amt;
allowedMul = Math.max(1, allowedMul); // 至少 1
minMul = Math.min(minMul, allowedMul);
}
return minMul != Long.MAX_VALUE ? (int) Math.min(minMul, Integer.MAX_VALUE) : 0;
}
private static long getUnitMultiplier(AEKey key) {
if (key instanceof AEItemKey) return 1L;
if (key instanceof AEFluidKey) return 1000L;
// 支持 Mekanism Chemical 反射安全
try {
// 反射判断扩展 Key 类型
if (key.getClass().getName().equals("me.ramidzkh.mekae2.ae2.MekanismKey")) {
if ("me.ramidzkh.mekae2.ae2.MekanismKey".equals(key.getClass().getName())) {
return 1000L;
}
// 根据需要继续增加
} catch (Exception ignored) {}
return 1L; // 默认单位
return 1L;
}
}
}

View File

@ -53,9 +53,7 @@
"ae2.accessor.PatternProviderMenuAccessor",
"ae2.autopattern.CraftingProviderListAccessor",
"ae2.autopattern.CraftingServiceGetProvidersMixin",
"ae2.autopattern.CraftingTreeNodeAccessor",
"ae2.autopattern.CraftingTreeNodeMixin",
"ae2.autopattern.CraftingTreeProcessMixin",
"ae2.autopattern.CraftingSimulationStateMixin",
"ae2.autopattern.PatternProviderLogicContainsRedirectMixin",
"ae2.compat.PatternProviderCompatMixin",
"ae2.compat.PatternProviderLogicCompatMixin",