Skip to content
Merged
Show file tree
Hide file tree
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
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,8 @@ replay_pid*
# Maven build directory
target/

.git-versioned-pom.xml
.git-versioned-pom.xml
dependency-reduced-pom.xml

# Addon Files
/NetworksExpansion/
12 changes: 12 additions & 0 deletions src/main/java/me/ddggdd135/slimeae/SlimeAEPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public final class SlimeAEPlugin extends JavaPlugin implements SlimefunAddon {
private SlimeAECommand slimeAECommand = new SlimeAECommand();
private PinnedManager pinnedManager;
private Metrics metrics;
private static boolean debug = false;

@Override
public void onEnable() {
Expand Down Expand Up @@ -317,12 +318,23 @@ public static PinnedManager getPinnedManager() {
return getInstance().pinnedManager;
}

/**
* 判断是否开启了调试模式
* @return 是否开启调试模式
*/
public static boolean isDebug() {
return debug;
}

public void reloadConfig0() {
// 保存默认配置
saveDefaultConfig();

reloadConfig();

// 重载调试模式配置
debug = getConfig().getBoolean("debug", false);

// 重载网络配置
NetworkInfo.reloadConfig();

Expand Down
166 changes: 154 additions & 12 deletions src/main/java/me/ddggdd135/slimeae/api/autocraft/AutoCraftingTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import me.ddggdd135.slimeae.api.interfaces.*;
import me.ddggdd135.slimeae.api.items.ItemRequest;
import me.ddggdd135.slimeae.api.items.ItemStorage;
import me.ddggdd135.slimeae.api.items.StorageCollection;
import me.ddggdd135.slimeae.core.NetworkInfo;
import me.ddggdd135.slimeae.core.items.MenuItems;
import me.ddggdd135.slimeae.utils.ItemUtils;
Expand Down Expand Up @@ -66,13 +67,57 @@ public AutoCraftingTask(@Nonnull NetworkInfo info, @Nonnull CraftingRecipe recip
List<CraftStep> newSteps = null;

List<CraftStep> usingSteps;
// 强制使存储缓存失效,确保获取最新数据快照
// (防止 dispose 归还物品后 F3 缓存仍保存旧数据)
info.getStorage().invalidateStorageCache();
info.getStorage().clearNotIncluded();

// === 调试日志 ===
java.util.logging.Logger debugLog = SlimeAEPlugin.getInstance().getLogger();
if (SlimeAEPlugin.isDebug()) {
debugLog.info("[AutoCraft-Debug] === 开始创建合成任务 ===");
debugLog.info("[AutoCraft-Debug] 配方输出: " + ItemUtils.getItemName(recipe.getOutput()[0]) + " x" + count);
debugLog.info("[AutoCraft-Debug] recipe在getRecipes中: " + info.getRecipes().contains(recipe));
debugLog.info("[AutoCraft-Debug] getRecipes大小: " + info.getRecipes().size());
ItemHashMap<Long> debugSnapshot = info.getStorage().getStorageUnsafe();
for (Map.Entry<ItemKey, Long> de : debugSnapshot.keyEntrySet()) {
if (de.getValue() > 0) {
debugLog.info("[AutoCraft-Debug] 存储内容: " + ItemUtils.getItemName(de.getKey().getItemStack()) + " = " + de.getValue());
}
}
}
// === 调试日志结束 ===

try {
newSteps = calcCraftSteps(recipe, count, new ItemStorage(info.getStorage()));
if (checkCraftStepsValid(newSteps, new ItemStorage(info.getStorage()))) usingSteps = newSteps;
else throw new IllegalStateException("新版算法出错,退回旧版算法");
} catch (Exception ignored) {
// 新版算法出错,退回旧版算法
usingSteps = match(recipe, count, new ItemStorage(info.getStorage()));
if (SlimeAEPlugin.isDebug()) {
debugLog.info("[AutoCraft-Debug] 新版算法异常: " + ignored.getClass().getSimpleName() + " - " + ignored.getMessage());
if (ignored instanceof NoEnoughMaterialsException nee) {
debugLog.info("[AutoCraft-Debug] 新版算法缺失材料:");
for (Map.Entry<ItemStack, Long> me : nee.getMissingMaterials().entrySet()) {
debugLog.info("[AutoCraft-Debug] " + ItemUtils.getItemName(me.getKey()) + " x " + me.getValue());
}
}
debugLog.info("[AutoCraft-Debug] 回退到旧版算法 match()");
}
try {
usingSteps = match(recipe, count, new ItemStorage(info.getStorage()));
if (SlimeAEPlugin.isDebug()) {
debugLog.info("[AutoCraft-Debug] 旧版算法成功,步骤数: " + usingSteps.size());
}
} catch (NoEnoughMaterialsException matchEx) {
if (SlimeAEPlugin.isDebug()) {
debugLog.info("[AutoCraft-Debug] 旧版算法也失败了! 缺失材料:");
for (Map.Entry<ItemStack, Long> me : matchEx.getMissingMaterials().entrySet()) {
debugLog.info("[AutoCraft-Debug] " + ItemUtils.getItemName(me.getKey()) + " x " + me.getValue());
}
}
throw matchEx;
}
}

craftingSteps = usingSteps;
Expand Down Expand Up @@ -137,14 +182,20 @@ private List<CraftStep> match(CraftingRecipe recipe, long count, ItemStorage sto

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;
for (ItemKey key : in.sourceKeySet()) {
long amount = storage.getStorageUnsafe().getOrDefault(key, 0L);
long need = in.getKey(key) * count;
if (amount < need) {
missing.addItem(new ItemKey(template), need - amount);
missing.addItem(key, need - amount);
}
}
// 如果所有材料够用但配方不可用,仍然报告所有输入为缺失
if (missing.getStorageUnsafe().isEmpty()) {
for (ItemKey key : in.sourceKeySet()) {
missing.addItem(key, in.getKey(key) * count);
}
}
throw new NoEnoughMaterialsException(missing.getStorageUnsafe());
Expand Down Expand Up @@ -268,14 +319,20 @@ private void calcCraftStep(CraftingRecipe recipe, long count, ItemStorage storag

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;
for (ItemKey key : in.sourceKeySet()) {
long amount = storage.getStorageUnsafe().getOrDefault(key, 0L);
long need = in.getKey(key) * count;
if (amount < need) {
missing.addItem(new ItemKey(template), need - amount);
missing.addItem(key, need - amount);
}
}
// 如果所有材料够用但配方不可用,仍然报告所有输入为缺失
if (missing.getStorageUnsafe().isEmpty()) {
for (ItemKey key : in.sourceKeySet()) {
missing.addItem(key, in.getKey(key) * count);
}
}
throw new NoEnoughMaterialsException(missing.getStorageUnsafe());
Expand All @@ -284,11 +341,32 @@ private void calcCraftStep(CraftingRecipe recipe, long count, ItemStorage storag
ItemStorage missing = new ItemStorage();
ItemHashMap<Long> in = recipe.getInputAmounts();

java.util.logging.Logger cLog = SlimeAEPlugin.getInstance().getLogger();
if (SlimeAEPlugin.isDebug()) {
cLog.info("[AutoCraft-Debug] calcCraftStep: 配方=" + ItemUtils.getItemName(recipe.getOutput()[0]) + " count=" + count);
cLog.info("[AutoCraft-Debug] calcCraftStep: storage快照大小=" + storage.getStorageUnsafe().size() + ", input种类=" + in.size());
}

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

if (SlimeAEPlugin.isDebug()) {
cLog.info("[AutoCraft-Debug] calcCraftStep: 材料=" + ItemUtils.getItemName(key.getItemStack())
+ " amount(存储中)=" + amount + " need=" + need
+ " keyHash=" + key.hashCode());
// 额外检查:遍历 storage 的 key 看看是否有"同物品但不同hash"的情况
for (Map.Entry<ItemKey, Long> se : storage.getStorageUnsafe().keyEntrySet()) {
if (se.getValue() > 0) {
boolean eq = key.equals(se.getKey());
cLog.info("[AutoCraft-Debug] storage key: " + ItemUtils.getItemName(se.getKey().getItemStack())
+ "=" + se.getValue() + " hash=" + se.getKey().hashCode()
+ " equals=" + eq);
}
}
}

if (amount >= need) {
storage.takeItem(new ItemRequest(key, need));
} else {
Expand All @@ -299,6 +377,10 @@ private void calcCraftStep(CraftingRecipe recipe, long count, ItemStorage storag

// 尝试合成缺少的材料
CraftingRecipe craftingRecipe = getRecipe(key.getItemStack());
if (SlimeAEPlugin.isDebug()) {
cLog.info("[AutoCraft-Debug] calcCraftStep: 尝试子合成 " + ItemUtils.getItemName(key.getItemStack())
+ " craftingRecipe=" + (craftingRecipe != null ? "found" : "null"));
}
if (craftingRecipe == null) {
missing.addItem(new ItemKey(key.getItemStack()), remainingNeed);
continue;
Expand Down Expand Up @@ -330,6 +412,9 @@ private void calcCraftStep(CraftingRecipe recipe, long count, ItemStorage storag

// 如果有缺少的材料就抛出异常
if (!missing.getStorageUnsafe().isEmpty()) {
if (SlimeAEPlugin.isDebug()) {
cLog.info("[AutoCraft-Debug] calcCraftStep: 缺失材料! 将抛出异常");
}
throw new NoEnoughMaterialsException(missing.getStorageUnsafe());
}
} finally {
Expand Down Expand Up @@ -608,7 +693,64 @@ public void dispose() {
Bukkit.getPluginManager().callEvent(e);

info.getAutoCraftingSessions().remove(this);
info.getTempStorage().addItem(storage.getStorageUnsafe(), true);

// 把材料直接推回 StorageCollection(而非通过 tempStorage 间接转移),
// 确保物品立即回到真实存储中,避免以下问题:
// 1. tempStorage 的 addItem 与 updateTempStorage 的 takeItem 并发竞态导致物品丢失或不可见
// 2. getStorageUnsafe() 缓存 (F3) 过期导致快照中不含已归还物品
// 3. notIncluded 负面缓存导致 takeItem/contains 错误跳过已归还物品
ItemHashMap<Long> toReturn = new ItemHashMap<>(storage.getStorageUnsafe());

// === 调试日志 ===
java.util.logging.Logger debugLog = SlimeAEPlugin.getInstance().getLogger();
if (SlimeAEPlugin.isDebug()) {
debugLog.info("[AutoCraft-Debug] === dispose() 开始归还物品 ===");
for (Map.Entry<ItemKey, Long> de : toReturn.keyEntrySet()) {
if (de.getValue() > 0) {
debugLog.info("[AutoCraft-Debug] 待归还: " + ItemUtils.getItemName(de.getKey().getItemStack()) + " = " + de.getValue());
}
}
}
// === 调试日志结束 ===

StorageCollection currentStorage = info.getStorage();
currentStorage.clearNotIncluded();
currentStorage.clearTakeAndPushCache();
currentStorage.pushItem(toReturn);
// pushItem 会将剩余量写回 toReturn 的 entry 中,
// 推不进去的物品放入 tempStorage 作为后备
ItemUtils.trim(toReturn);

// === 调试日志 ===
if (!toReturn.isEmpty()) {
if (SlimeAEPlugin.isDebug()) {
debugLog.info("[AutoCraft-Debug] pushItem后仍有剩余(将放入tempStorage):");
for (Map.Entry<ItemKey, Long> de : toReturn.keyEntrySet()) {
debugLog.info("[AutoCraft-Debug] 剩余: " + ItemUtils.getItemName(de.getKey().getItemStack()) + " = " + de.getValue());
}
}
info.getTempStorage().addItem(toReturn, true);
} else {
if (SlimeAEPlugin.isDebug()) {
debugLog.info("[AutoCraft-Debug] 所有物品已成功推回存储");
}
}
// === 调试日志结束 ===

currentStorage.invalidateStorageCache();

// === 调试日志: 验证归还后存储 ===
if (SlimeAEPlugin.isDebug()) {
currentStorage.invalidateStorageCache();
ItemHashMap<Long> afterReturn = currentStorage.getStorageUnsafe();
debugLog.info("[AutoCraft-Debug] 归还后存储快照:");
for (Map.Entry<ItemKey, Long> de : afterReturn.keyEntrySet()) {
if (de.getValue() > 0) {
debugLog.info("[AutoCraft-Debug] " + ItemUtils.getItemName(de.getKey().getItemStack()) + " = " + de.getValue());
}
}
debugLog.info("[AutoCraft-Debug] === dispose() 完成 ===");
}

Bukkit.getScheduler()
.runTask(SlimeAEPlugin.getInstance(), () -> menu.getInventory().close());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@
import me.ddggdd135.guguslimefunlib.api.ItemHashMap;
import me.ddggdd135.slimeae.utils.CraftItemStackUtils;
import me.ddggdd135.slimeae.utils.ItemUtils;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;

public class CraftingRecipe {
private final CraftType craftType;
private final ItemStack[] input;
private final ItemStack[] output;
private ItemHashMap<Long> inputAmounts;
private ItemHashMap<Long> outputAmounts;

public CraftingRecipe(@Nonnull CraftType craftType, @Nonnull ItemStack[] input, @Nonnull ItemStack[] output) {
this.craftType = craftType;
Expand Down Expand Up @@ -42,16 +41,32 @@ public ItemStack[] getOutput() {

@Nonnull
public ItemHashMap<Long> getInputAmounts() {
if (inputAmounts == null) inputAmounts = ItemUtils.getAmounts(input);

return inputAmounts;
return ItemUtils.getAmounts(toBukkitCopy(input));
}

@Nonnull
public ItemHashMap<Long> getOutputAmounts() {
if (outputAmounts == null) outputAmounts = ItemUtils.getAmounts(output);
return ItemUtils.getAmounts(toBukkitCopy(output));
}

return outputAmounts;
/**
* 将 CraftItemStack 数组转换为纯 Bukkit ItemStack 数组。
* 避免 ItemKey 内部持有 CraftItemStack 的 NMS 引用,
* 防止 NMS 对象被外部操作修改导致 ItemKey.equals() 失效。
*/
private static ItemStack[] toBukkitCopy(ItemStack[] craftStacks) {
ItemStack[] result = new ItemStack[craftStacks.length];
for (int i = 0; i < craftStacks.length; i++) {
ItemStack cs = craftStacks[i];
if (cs == null || cs.getType().isAir()) {
result[i] = new ItemStack(Material.AIR);
} else {
// 使用 new ItemStack(cs) 创建纯 Bukkit ItemStack 副本,
// 与 CraftItemStack 完全解耦
result[i] = new ItemStack(cs);
}
}
return result;
}

@Override
Expand All @@ -67,8 +82,26 @@ public boolean equals(Object o) {
@Override
public int hashCode() {
int result = Objects.hash(craftType);
result = 31 * result + Arrays.hashCode(input);
result = 31 * result + Arrays.hashCode(output);
result = 31 * result + stableArrayHashCode(input);
result = 31 * result + stableArrayHashCode(output);
return result;
}

/**
* 计算 ItemStack 数组的稳定 hashCode。
* 对 AIR 类型统一使用固定 hashCode(与 equals 中 AIR amount=0 的处理保持一致),
* 避免因 CraftItemStack 的可变 amount 导致 hashCode 不稳定。
*/
private static int stableArrayHashCode(ItemStack[] array) {
if (array == null) return 0;
int h = 1;
for (ItemStack item : array) {
if (item == null || item.getType().isAir()) {
h = 31 * h; // AIR/null 统一为 0
} else {
h = 31 * h + item.hashCode();
}
}
return h;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public void pushItem(@Nonnull ItemInfo itemInfo) {
stored += toAdd;
storageData.setStored(stored);
storages.putKey(key, amount + toAdd);
itemInfo.setAmount((int) (itemInfo.getAmount() - toAdd));
itemInfo.setAmount(itemInfo.getAmount() - toAdd);
SlimeAEPlugin.getStorageCellStorageDataController().markDirty(storageData);
trim(key);
}
Expand Down
Loading
Loading