Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 168 additions & 4 deletions src/main/java/me/ddggdd135/slimeae/api/autocraft/AutoCraftingTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import me.ddggdd135.guguslimefunlib.api.AEMenu;
Expand Down Expand Up @@ -48,19 +49,34 @@ public class AutoCraftingTask implements IDisposable {
private int running = 0;
private int virtualRunning = 0;
private int virtualProcess = 0;
private final AEMenu menu = new AEMenu("&e合成任务");
private final AEMenu menu;
private boolean isCancelling = false;
private final Set<CraftingRecipe> craftingPath = new HashSet<>();
private ItemStorage storage;
private int failTimes;
private CraftTaskCalcData recipeCalcData = new CraftTaskCalcData();

public AutoCraftingTask(@Nonnull NetworkInfo info, @Nonnull CraftingRecipe recipe, long count) {
this.info = info;
this.recipe = recipe;
this.count = count;
menu.setSize(54);
menu.addMenuCloseHandler(player -> dispose());
craftingSteps = match(recipe, count, new ItemStorage(info.getStorage()));

// 先使用旧版算法,验证checkCraftStepsValid是否有误
// 如果无误可以将这一步放在新版算法出错后,节省一次计算(当前版本计算量是原来的2倍)
List<CraftStep> oldSteps = match(recipe, count, new ItemStorage(info.getStorage()));

// TODO 删除调试信息
String errorInfo;
if (!checkCraftStepsValid(oldSteps, new ItemStorage(info.getStorage()))) {
errorInfo = "检查合成步骤出错(这是一条开发者消息,如果您看到这条信息,请截图告知开发者,多谢)";
} else errorInfo = "";

List<CraftStep> newSteps = calcCraftSteps(recipe, count, new ItemStorage(info.getStorage()));

// 如果新版算法出错,则退回旧版算法
if (!checkCraftStepsValid(newSteps, new ItemStorage(info.getStorage()))) craftingSteps = oldSteps;
else craftingSteps = newSteps;

this.storage = new ItemStorage();

// 所有材料都能拿到
Expand All @@ -72,6 +88,16 @@ public AutoCraftingTask(@Nonnull NetworkInfo info, @Nonnull CraftingRecipe recip
}

storage = info.getStorage().takeItem(ItemUtils.createRequests(storage.copyStorage()));

// TODO 删除调试信息
boolean debug_initialItemEnough;
debug_initialItemEnough = checkCraftStepsValid(craftingSteps, new ItemStorage(storage));

String TitleInfo = craftingSteps == newSteps ? "&2(新版算法)" : "&7(旧版算法)";
if (debug_initialItemEnough) menu = new AEMenu("&e合成任务" + TitleInfo + errorInfo);
else menu = new AEMenu("&e合成任务" + TitleInfo + "&c(拿取材料失败,合成可能卡住)" + errorInfo);
menu.setSize(54);
menu.addMenuCloseHandler(player -> dispose());
}

@Nonnull
Expand All @@ -93,6 +119,22 @@ public List<CraftStep> getCraftingSteps() {
return craftingSteps;
}

private boolean checkCraftStepsValid(List<CraftStep> steps, ItemStorage storage) {
for (CraftStep step : steps) {
for (Map.Entry<ItemKey, Long> input :
step.getRecipe().getInputAmounts().keyEntrySet()) {
long amount = input.getValue() * step.getAmount();
if (amount > storage.getStorageUnsafe().getOrDefault(input.getKey(), 0L)) return false;
storage.takeItem(new ItemRequest(input.getKey(), amount));
}
for (Map.Entry<ItemKey, Long> output :
step.getRecipe().getOutputAmounts().keyEntrySet()) {
storage.addItem(output.getKey(), output.getValue() * step.getAmount());
}
}
return true;
}

private List<CraftStep> match(CraftingRecipe recipe, long count, ItemStorage storage) {
if (!craftingPath.add(recipe)) {
throw new IllegalStateException("检测到循环依赖的合成配方");
Expand Down Expand Up @@ -173,6 +215,128 @@ private List<CraftStep> match(CraftingRecipe recipe, long count, ItemStorage sto
}
}

private class CraftTaskCalcData {
public class CraftTaskCalcItem {
List<CraftingRecipe> before = new ArrayList<>();
CraftingRecipe thisone;
long count;

CraftTaskCalcItem(CraftingRecipe recipe, long count) {
this.thisone = recipe;
this.count = count;
}
}

public final Map<CraftingRecipe, CraftTaskCalcItem> recipeMap = new ConcurrentHashMap<>();

public void addRecipe(CraftingRecipe recipe, long count, CraftingRecipe parent) {
if (recipeMap.containsKey(recipe)) { // 加到之前的合成上
recipeMap.get(recipe).count += count;
} else { // 第一次合成
recipeMap.put(recipe, new CraftTaskCalcItem(recipe, count));
recipeMap.get(parent).before.add(recipe);
}
}

public void rootRecipe(CraftingRecipe recipe, long count) {
recipeMap.clear();
recipeMap.put(recipe, new CraftTaskCalcItem(recipe, count));
}
}

private List<CraftStep> calcCraftSteps(CraftingRecipe recipe, long count, ItemStorage storage) {
recipeCalcData.rootRecipe(recipe, count);
calcCraftStep(recipe, count, storage);
return unpackCraftSteps(recipe);
}

private List<CraftStep> unpackCraftSteps(CraftingRecipe recipe) {
List<CraftStep> result = new ArrayList<>();
CraftTaskCalcData.CraftTaskCalcItem recipeInfo = recipeCalcData.recipeMap.get(recipe);
for (CraftingRecipe beforeRecipe : recipeInfo.before) {
result.addAll(unpackCraftSteps(beforeRecipe));
}
result.add(new CraftStep(recipe, recipeInfo.count));
return result;
}

private void calcCraftStep(CraftingRecipe recipe, long count, ItemStorage storage) {
if (!craftingPath.add(recipe)) {
throw new IllegalStateException("检测到循环依赖的合成配方");
}

try {
if (!info.getRecipes().contains(recipe)) {
// 记录直接缺少的材料
ItemStorage missing = new ItemStorage();
ItemHashMap<Long> in = recipe.getInputAmounts();
for (ItemStack template : in.keySet()) {
long amount = storage.getStorageUnsafe().getOrDefault(template, 0L);
long need = in.get(template) * count;
if (amount < need) {
missing.addItem(new ItemKey(template), need - amount);
}
}
throw new NoEnoughMaterialsException(missing.getStorageUnsafe());
}

ItemStorage missing = new ItemStorage();
ItemHashMap<Long> in = recipe.getInputAmounts();

// 遍历所需材料
for (ItemKey key : in.sourceKeySet()) {
long amount = storage.getStorageUnsafe().getOrDefault(key, 0L);
long need = in.getKey(key) * count;

if (amount >= need) {
storage.takeItem(new ItemRequest(key, need));
} else {
long remainingNeed = need - amount;
if (amount > 0) {
storage.takeItem(new ItemRequest(key, amount));
}

// 尝试合成缺少的材料
CraftingRecipe craftingRecipe = getRecipe(key.getItemStack());
if (craftingRecipe == null) {
missing.addItem(new ItemKey(key.getItemStack()), remainingNeed);
continue;
}

ItemHashMap<Long> output = craftingRecipe.getOutputAmounts();
ItemHashMap<Long> input = craftingRecipe.getInputAmounts();

// 计算需要合成多少次
long out = output.getKey(key) - input.getOrDefault(key, 0L);
long countToCraft = (long) Math.ceil(remainingNeed / (double) out);

try {
recipeCalcData.addRecipe(craftingRecipe, countToCraft, recipe);
calcCraftStep(craftingRecipe, countToCraft, storage);
for (Map.Entry<ItemKey, Long> o : output.keyEntrySet()) {
storage.addItem(o.getKey(), o.getValue() * countToCraft);
}
storage.takeItem(new ItemRequest(key, remainingNeed));
} catch (NoEnoughMaterialsException e) {
// 合并子合成缺少的材料
for (Map.Entry<ItemStack, Long> entry :
e.getMissingMaterials().entrySet()) {
missing.addItem(new ItemKey(entry.getKey()), entry.getValue());
}
}
}
}

// 如果有缺少的材料就抛出异常
if (!missing.getStorageUnsafe().isEmpty()) {
throw new NoEnoughMaterialsException(missing.getStorageUnsafe());
}
} finally {
// 无论是否成功,都要从路径中移除当前配方
craftingPath.remove(recipe);
}
}

@Nullable private CraftingRecipe getRecipe(@Nonnull ItemStack itemStack) {
return info.getRecipeFor(itemStack);
}
Expand Down