From 07f3f792eb958483e5fc0dca086e2d351ebea896 Mon Sep 17 00:00:00 2001 From: chzn Date: Wed, 30 Jul 2025 12:15:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E6=96=B0=E7=89=88?= =?UTF-8?q?=E5=90=88=E6=88=90=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/autocraft/AutoCraftingTask.java | 168 +++++++++++++++++- 1 file changed, 164 insertions(+), 4 deletions(-) diff --git a/src/main/java/me/ddggdd135/slimeae/api/autocraft/AutoCraftingTask.java b/src/main/java/me/ddggdd135/slimeae/api/autocraft/AutoCraftingTask.java index bc972115..8cc86853 100644 --- a/src/main/java/me/ddggdd135/slimeae/api/autocraft/AutoCraftingTask.java +++ b/src/main/java/me/ddggdd135/slimeae/api/autocraft/AutoCraftingTask.java @@ -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; @@ -48,19 +49,35 @@ 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 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 oldSteps = match(recipe, count, new ItemStorage(info.getStorage())); + + //TODO 删除调试信息 + String errorInfo; + if(!checkCraftStepsValid(oldSteps,new ItemStorage(info.getStorage()))){ + errorInfo="检查合成步骤出错(这是一条开发者消息,如果您看到这条信息,请截图告知开发者,多谢)"; + }else errorInfo=""; + + List newSteps = calcCraftSteps(recipe, count, new ItemStorage(info.getStorage())); + + // 如果新版算法出错,则退回旧版算法 + if(!checkCraftStepsValid(newSteps,new ItemStorage(info.getStorage()))) + craftingSteps = oldSteps; + else craftingSteps = newSteps; + this.storage = new ItemStorage(); // 所有材料都能拿到 @@ -72,6 +89,17 @@ 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 @@ -93,6 +121,21 @@ public List getCraftingSteps() { return craftingSteps; } + private boolean checkCraftStepsValid(List steps, ItemStorage storage) { + for(CraftStep step : steps) { + for(Map.Entry 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 output : step.getRecipe().getOutputAmounts().keyEntrySet()) { + storage.addItem(output.getKey(), output.getValue() * step.getAmount()); + } + } + return true; + } + private List match(CraftingRecipe recipe, long count, ItemStorage storage) { if (!craftingPath.add(recipe)) { throw new IllegalStateException("检测到循环依赖的合成配方"); @@ -173,6 +216,122 @@ private List match(CraftingRecipe recipe, long count, ItemStorage sto } } + + private class CraftTaskCalcData{ + public class CraftTaskCalcItem{ + List before=new ArrayList<>(); + CraftingRecipe thisone; + long count; + CraftTaskCalcItem(CraftingRecipe recipe, long count){ + this.thisone=recipe; + this.count=count; + } + } + public final Map 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 calcCraftSteps(CraftingRecipe recipe, long count, ItemStorage storage){ + recipeCalcData.rootRecipe(recipe, count); + calcCraftStep(recipe, count, storage); + return unpackCraftSteps(recipe); + } + private List unpackCraftSteps(CraftingRecipe recipe){ + List 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 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 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 output = craftingRecipe.getOutputAmounts(); + ItemHashMap 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 o : output.keyEntrySet()) { + storage.addItem(o.getKey(), o.getValue() * countToCraft); + } + storage.takeItem(new ItemRequest(key, remainingNeed)); + } catch (NoEnoughMaterialsException e) { + // 合并子合成缺少的材料 + for (Map.Entry 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); } @@ -446,3 +605,4 @@ public void dispose() { .runTask(SlimeAEPlugin.getInstance(), () -> menu.getInventory().close()); } } +