From 572b6e6f17b455e97bba63fd7b9e0c4befba5931 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Fri, 19 Sep 2025 18:16:18 +0800 Subject: [PATCH 01/34] =?UTF-8?q?fix:=20=E4=BD=BF=E7=94=A8=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89I/O=E5=AD=98=E5=82=A8=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=99=A8=E6=9B=BF=E4=BB=A3=E5=8E=9F=E6=9C=89SavedData?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/extendedae_plus/ExtendedAEPlus.java | 10 +- .../InfinityBigIntegerCellInventory.java | 235 +++++++++--------- .../ae/items/InfinityBigIntegerCellItem.java | 27 +- .../util/storage/InfinityDataStorage.java | 4 +- .../util/storage/InfinityStorageManager.java | 143 ++++++----- 5 files changed, 206 insertions(+), 213 deletions(-) diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index 25d675e..ca77fbf 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -53,12 +53,11 @@ public class ExtendedAEPlus { // 注册到Forge事件总线 MinecraftForge.EVENT_BUS.register(this); MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad); - // 注册通用配置 - ModConfig.init(); - // 注册 InfinityBigIntegerCellInventory 的事件监听(tick flush 与停止时 flush) + // 注册每秒合并持久化队列的事件监听(Server tick end + stopping) MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick); MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerStopping); -// ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC); + // 注册通用配置 + ModConfig.init(); } /** @@ -120,7 +119,8 @@ public class ExtendedAEPlus { // 在世界加载时注册/加载 SavedData private static void onLevelLoad(LevelEvent.Load event) { if (event.getLevel() instanceof ServerLevel serverLevel) { - InfinityStorageManager.getForLevel(serverLevel); + // 初始化自定义的文件 I/O 存储管理器 + InfinityStorageManager.INSTANCE.initFromWorld(serverLevel); } } } diff --git a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java index 2cbb417..d23c236 100644 --- a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java +++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java @@ -24,10 +24,10 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.text.DecimalFormat; +import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; -import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; /** * InfinityBigIntegerCellInventory *

@@ -48,6 +48,8 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 待持久化队列(用于 debounce:在服务器 tick 中合并持久化) private static final ConcurrentLinkedQueue PENDING_PERSIST = new ConcurrentLinkedQueue<>(); + // 用于按 tick 计数,每 20 tick(约 1 秒)触发一次合并写入 + private static int TICK_COUNTER = 0; // 数字格式化对象,保留两位小数(复用以减少对象分配) private static final DecimalFormat DF = new DecimalFormat("#.##"); @@ -59,8 +61,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell { private Object2ObjectMap storedMap = null; // 标记是否已持久化到 SavedData private boolean isPersisted = true; - // 缓存的总存储量,避免每次调用进行全表扫描 - private BigInteger totalStored = BigInteger.ZERO; + /** * 私有构造器:通过 createInventory 工厂方法调用 @@ -70,59 +71,9 @@ public class InfinityBigIntegerCellInventory implements StorageCell { */ private InfinityBigIntegerCellInventory(ItemStack stack, ISaveProvider saveProvider) { this.stack = stack; - container = saveProvider; - // 不在构造时创建 storedMap,推迟到实际访问或首次写入时初始化 + this.container = saveProvider; this.storedMap = null; - } - - // 创建存储单元库存实例的静态方法 - static InfinityBigIntegerCellInventory createInventory(ItemStack stack, ISaveProvider saveProvider) { - if (stack.getItem() instanceof InfinityBigIntegerCellItem) { - return new InfinityBigIntegerCellInventory(stack, saveProvider); - } - return null; - } - - // 获取全局存储实例 - private static InfinityStorageManager getStorageInstance() { - return InfinityStorageManager.INSTANCE; - } - - // 服务器 tick 回调:合并并执行待持久化项 - public static void onServerTick(TickEvent.ServerTickEvent event) { - if (event.phase != TickEvent.Phase.END) return; - InfinityBigIntegerCellInventory inv; - // 处理本次 tick 中的全部待持久化项 - while ((inv = PENDING_PERSIST.poll()) != null) { - try { - if (!inv.isPersisted) { - inv.persist(); - } - } catch (Throwable ignored) { - LOGGER.info("InfinityBigIntegerCellInventory onServerTick error: {}", ignored.getMessage()); - } - } - } - - // 在服务器停止时被调用,立即强制持久化队列中的所有实例 - public static void onServerStopping(ServerStoppingEvent event) { - InfinityBigIntegerCellInventory inv; - while ((inv = PENDING_PERSIST.poll()) != null) { - try { - if (!inv.isPersisted) { - inv.persist(); - } - } catch (Throwable ignored) { - LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error1: {}", ignored.getMessage()); - } - } - // 额外尝试将全局存储管理器标记为脏以确保 SavedData 被写回(在单人模式下可能直接由系统触发) - try { - var stor = getStorageInstance(); - if (stor != null) stor.setDirty(); - } catch (Throwable ignored) { - LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error2: {}", ignored.getMessage()); - } + initData(); } // 将 BigInteger 格式化为带单位的字符串,保留两位小数 @@ -142,14 +93,24 @@ public class InfinityBigIntegerCellInventory implements StorageCell { return DF.format(bd.doubleValue()) + units[idx]; } + // 创建存储单元库存实例的静态方法 + static InfinityBigIntegerCellInventory createInventory(ItemStack stack, ISaveProvider saveProvider) { + return new InfinityBigIntegerCellInventory(stack, saveProvider); + } + + private void initData() { + if (!hasUUID()) { + getCellStoredMap(); + } + } + // 获取当前存储单元的数据存储对象 private InfinityDataStorage getCellStorage() { if (this.getUUID() == null) { // 如果没有UUID,返回空存储 return InfinityDataStorage.EMPTY; } else { - // 否则获取或创建对应UUID的存储 - return getStorageInstance().getOrCreateCell(getUUID()); + return InfinityStorageManager.INSTANCE.getOrCreateCell(this.getUUID()); } } @@ -176,34 +137,37 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 判断物品堆栈是否有UUID public boolean hasUUID() { - return stack.hasTag() && stack.getOrCreateTag().contains("uuid"); + return this.stack.hasTag() && this.stack.getOrCreateTag().contains("uuid"); } // 获取物品堆栈的UUID public UUID getUUID() { - if (this.hasUUID()) - return stack.getOrCreateTag().getUUID("uuid"); - else + if (this.hasUUID()) { + return this.stack.getOrCreateTag().getUUID("uuid"); + } else { return null; + } } // 获取或初始化存储映射 private Object2ObjectMap getCellStoredMap() { - if (storedMap == null) { - storedMap = new Object2ObjectOpenHashMap<>(); + if (this.storedMap == null) { + this.storedMap = new Object2ObjectOpenHashMap<>(); this.loadCellStoredMap(); } - return storedMap; + return this.storedMap; } // 从存储中加载物品映射 private void loadCellStoredMap() { boolean corruptedTag = false; // 标记数据是否损坏 - if (!stack.hasTag()) return; + if (!this.stack.hasTag()) + return; ListTag keys = this.getCellStorage().keys; ListTag amounts = this.getCellStorage().amounts; - int len = Math.min(keys.size(), amounts.size()); - for (int i = 0; i < len; i++) { + + + for (int i = 0; i < amounts.size(); i++) { AEKey key = AEKey.fromTagGeneric(keys.getCompound(i)); CompoundTag amtTag = amounts.getCompound(i); try { @@ -221,15 +185,13 @@ public class InfinityBigIntegerCellInventory implements StorageCell { corruptedTag = true; } else { // storedMap 已在 getCellStoredMap() 中初始化,直接使用字段以避免额外方法开销 - storedMap.put(key, amount); - // 更新缓存的总数 - totalStored = totalStored.add(amount); + getCellStoredMap().put(key, amount); } } catch (NumberFormatException ex) { corruptedTag = true; } } - // 如果有损坏,保存修正后的数据 + // 如果有损坏,尝试保存修正后的数据;若全局管理器尚未就绪则保守处理 if (corruptedTag) { this.saveChanges(); } @@ -238,14 +200,37 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 标记数据需要保存,并通知容器或直接持久化 private void saveChanges() { // 标记为未持久化,交由容器或延迟任务合并写入以减少 I/O - isPersisted = false; - if (container != null) { - // 当存在容器时,优先让容器统一处理持久化 - container.saveChanges(); - } else { - // 如果没有容器,入队等待服务器 tick 在主线程统一持久化,避免频繁 I/O - if (!PENDING_PERSIST.contains(this)) { - PENDING_PERSIST.offer(this); + this.isPersisted = false; + // 将本实例加入待处理队列(去重) + if (!PENDING_PERSIST.contains(this)) { + PENDING_PERSIST.add(this); + } + if (this.container != null) { + // 当存在容器时,仍然通知容器以便 AE2 在合并时回调 persist() + this.container.saveChanges(); + } + } + + /** + * 每个服务器 tick 调用一次,用于按秒合并并执行待持久化项的 persist() + */ + public static void onServerTick(TickEvent.ServerTickEvent event) { + if (event.phase != TickEvent.Phase.END) return; + TICK_COUNTER++; + if (TICK_COUNTER % 20 != 0) return; // 每 20 tick(约 1 秒)执行一次 + onServerStopping(null); + } + + /** + * 在服务器停止时强制刷新所有待持久化项 + */ + public static void onServerStopping(ServerStoppingEvent event) { + InfinityBigIntegerCellInventory inv; + while ((inv = PENDING_PERSIST.poll()) != null) { + try { + inv.persist(); + } catch (Exception e) { + e.printStackTrace(); } } } @@ -287,20 +272,25 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 持久化存储单元数据到全局存储 @Override public void persist() { - if (this.isPersisted) + if (this.isPersisted) { return; + } Object2ObjectMap map = this.getCellStoredMap(); if (map.isEmpty()) { - // 如果存储为空,移除UUID和全局存储中的数据 + // 如果存储为空,保守处理:写回空的 persisted 数据但不要从 ItemStack 上移除 uuid if (this.hasUUID()) { - getStorageInstance().removeCell(getUUID()); - if (stack.getTag() != null) { - stack.getTag().remove("uuid"); - // 移除缓存的 total 字段 - stack.getTag().remove("total"); + // 如果存储为空,移除UUID和全局存储中的数据,并清理缓存的 types/total + if (hasUUID()) { + InfinityStorageManager.INSTANCE.removeCell(getUUID()); + if (this.stack.getTag() != null) { + this.stack.getTag().remove("uuid"); + this.stack.getTag().remove("types"); + this.stack.getTag().remove("total"); + } + initData(); } + return; } - return; } // 构建要保存的Key和数量列表(混合表示:long 或 string) ListTag amountTags = new ListTag(); @@ -318,42 +308,58 @@ public class InfinityBigIntegerCellInventory implements StorageCell { amountTags.add(amt); } } - // 如果没有Key,更新为空存储,否则保存数据 if (keys.isEmpty()) { - getStorageInstance().updateCell(this.getUUID(), new InfinityDataStorage()); + InfinityStorageManager.INSTANCE.updateCell(this.getUUID(), new InfinityDataStorage()); + // 清理缓存 + if (this.stack.getTag() != null) { + this.stack.getTag().remove("types"); + this.stack.getTag().remove("total"); + } } else { // amounts 现在为 CompoundTag 列表 - getStorageInstance().modifyCell(this.getUUID(), keys, amountTags); - } - // 将缓存的 totalStored 同步到 ItemStack 的 NBT,优先使用 long - if (stack.getOrCreateTag() != null) { - if (totalStored.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) { - stack.getOrCreateTag().putLong("total", totalStored.longValue()); - } else { - stack.getOrCreateTag().putString("total", totalStored.toString()); + InfinityStorageManager.INSTANCE.modifyCell(this.getUUID(), keys, amountTags); + // 缓存类型数量与总量到 ItemStack 的 NBT,避免每次 tooltip 或展示时重新统计 + try { + if (this.stack.getTag() == null) { + this.stack.setTag(new CompoundTag()); + } + int typesCount = keys.size(); + this.stack.getOrCreateTag().putInt("types", typesCount); + BigInteger total = BigInteger.ZERO; + for (Map.Entry e : map.object2ObjectEntrySet()) { + BigInteger v = e.getValue(); + if (v.compareTo(BigInteger.ZERO) > 0) { + total = total.add(v); + } + } + if (total.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) { + this.stack.getOrCreateTag().putLong("total", total.longValue()); + } else { + this.stack.getOrCreateTag().putString("total", total.toString()); + } + } catch (Exception ignored) { } - // 将当前已存储的不同物品种类数缓存到 NBT(键名: "types"),用于客户端 tooltip 显示 - int typesCount = this.getCellStoredMap().size(); - stack.getOrCreateTag().putInt("types", typesCount); } - isPersisted = true; + this.isPersisted = true; } // 插入物品到存储单元 @Override public long insert(AEKey what, long amount, Actionable mode, IActionSource source) { // 数量为0或类型不匹配直接返回 - if (amount == 0) + if (amount == 0) { return 0; + } // 不允许存储无限单元自身 - if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) + if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) { return 0; - // 如果没有UUID,生成UUID并初始化存储 + } + // 如果没有UUID,生成UUID并初始化存储(延迟创建全局存储以避免在 manager 未就绪时 NPE) if (!this.hasUUID()) { - stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID()); - getStorageInstance().getOrCreateCell(getUUID()); + this.stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID()); + InfinityStorageManager.INSTANCE.getOrCreateCell(getUUID()); // 确保 storedMap 初始化并从持久层加载数据 - this.getCellStoredMap(); + loadCellStoredMap(); } Object2ObjectMap map = this.getCellStoredMap(); BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO); @@ -361,8 +367,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 实际插入,更新数量并保存 BigInteger newAmount = currentAmount.add(BigInteger.valueOf(amount)); map.put(what, newAmount); - // 更新 cached total - totalStored = totalStored.add(BigInteger.valueOf(amount)); this.saveChanges(); } return amount; @@ -383,11 +387,8 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } else { ret = currentAmount.longValue(); } - if (mode == Actionable.MODULATE) { + if (mode == appeng.api.config.Actionable.MODULATE) { map.remove(what); - // 更新 cached total - // 如果 currentAmount 大于 Long.MAX_VALUE,totalStored 减去 currentAmount 会保留大整数 - totalStored = totalStored.subtract(currentAmount); this.saveChanges(); } return ret; @@ -395,8 +396,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 提取部分 if (mode == Actionable.MODULATE) { map.put(what, currentAmount.subtract(requested)); - // 更新 cached total - totalStored = totalStored.subtract(requested); this.saveChanges(); } return amount; @@ -404,10 +403,4 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } return 0; } - - // 获取存储单元内所有物品的总数量(格式化字符串) - public String getTotalStorage() { - // 使用缓存的 totalStored,避免每次全表扫描 - return formatBigInteger(totalStored); - } } diff --git a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java index dde5b96..f76df19 100644 --- a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java +++ b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java @@ -1,7 +1,6 @@ package com.extendedae_plus.ae.items; import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory; -import com.google.common.base.Preconditions; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.LongTag; @@ -14,7 +13,6 @@ import net.minecraft.world.level.Level; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; -import java.math.BigInteger; import java.util.List; public class InfinityBigIntegerCellItem extends Item { @@ -39,15 +37,14 @@ public class InfinityBigIntegerCellItem extends Item { tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon1")); tooltip.add(Component.translatable("tooltip.extendedae_plus.infinity_biginteger_cell.summon2")); - Preconditions.checkArgument(stack.getItem() == this); - // 仅在 ItemStack 自身存在 UUID 时显示 UUID,避免触发持久化或加载逻辑 + // 优先使用 ItemStack 的 NBT 缓存信息显示 tooltip(客户端不应触发世界 I/O) CompoundTag tag = stack.getTag(); if (tag != null && tag.contains("uuid")) { String uuidStr = tag.getUUID("uuid").toString(); tooltip.add( Component.literal("UUID: ").withStyle(ChatFormatting.GRAY).append(Component.literal(uuidStr).withStyle(ChatFormatting.YELLOW)) ); - // 读取并显示已缓存的种类数量(types),表示当前存储了多少种不同的 AEKey + // types 表示缓存的种类数 if (tag.contains("types")) { try { int types = tag.getInt("types"); @@ -55,27 +52,25 @@ public class InfinityBigIntegerCellItem extends Item { Component.literal("Types: ").withStyle(ChatFormatting.GRAY).append(Component.literal(String.valueOf(types)).withStyle(ChatFormatting.GREEN)) ); } catch (Exception ignored) { - // ignore malformed value } } - // 读取并显示已缓存的 total(支持 long 或 string),使用格式化函数展示友好单位 + // total 支持 long 或 string 两种表现形式 if (tag.contains("total")) { - BigInteger total = BigInteger.ZERO; - Tag t = tag.get("total"); try { + java.math.BigInteger total; + Tag t = tag.get("total"); if (t instanceof LongTag) { - total = BigInteger.valueOf(tag.getLong("total")); + total = java.math.BigInteger.valueOf(tag.getLong("total")); } else { String s = tag.getString("total"); - total = new BigInteger(s); + total = new java.math.BigInteger(s); } + String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total); + tooltip.add( + Component.literal("Byte: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA)) + ); } catch (Exception ignored) { - // 解析失败保持为 0 } - String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total); - tooltip.add( - Component.literal("Byte: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA)) - ); } } } diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java index 4ef9036..71ec86a 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java @@ -44,8 +44,8 @@ public class InfinityDataStorage { */ public CompoundTag serializeNBT() { CompoundTag nbt = new CompoundTag(); - nbt.put("keys", keys); - nbt.put("amounts", amounts); + nbt.put("keys", this.keys); + nbt.put("amounts", this.amounts); return nbt; } diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java index e24a5ab..9aa2f64 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -1,11 +1,18 @@ package com.extendedae_plus.util.storage; +import com.extendedae_plus.ExtendedAEPlus; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.Tag; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.saveddata.SavedData; -import org.jetbrains.annotations.NotNull; +import net.minecraft.world.level.storage.LevelResource; +import org.jetbrains.annotations.Nullable; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -13,88 +20,89 @@ import java.util.UUID; /** * InfinityStorageManager *

- * 世界级别的持久化容器,集中管理所有 InfinityBigInteger 存储单元的序列化数据。 - * 功能要点: - * - 在世界加载时从存档恢复所有 cell 的数据 - * - 提供按 UUID 获取/创建单个 cell 的数据容器 - * - 在世界保存时将内存数据打包为 NBT 写回存档 + * 替代之前基于 SavedData 的实现,本类使用手动文件 I/O 在 world 目录下保存 NBT 数据, + * 以避免依赖 Minecraft 的 SavedData 机制。 + * 数据保持与之前兼容的 NBT 结构:根 Compound 包含 "list" => ListTag of Compound { uuid, data } */ -public class InfinityStorageManager extends SavedData { +public class InfinityStorageManager { + + public static final String FILE_NAME = "eap_infinity_biginteger_cells.dat"; /** - * SavedData 文件名常量 - */ - public static final String FILE_NAME = "eap_infinity_biginteger_cells"; - /** - * 全局单例实例(在世界加载时由 InfiniteBigIntegerStorageCell.onLevelLoad 填充) - */ - public static InfinityStorageManager INSTANCE = null; - /** - * UUID -> 数据 的内存映射 + * 全局单例,由 mod 在 world load 时初始化 */ + public static volatile InfinityStorageManager INSTANCE = new InfinityStorageManager(); + private final Map cells = new HashMap<>(); - public InfinityStorageManager() { - setDirty(); + private Path saveFilePath = null; + + private InfinityStorageManager() {} + + /** + * 初始化并从 world 保存目录加载数据;若文件不存在则保持空状态 + */ + public void initFromWorld(@Nullable ServerLevel serverLevel) { + if (serverLevel == null) return; + try { + File worldFolder = serverLevel.getServer().getWorldPath(LevelResource.ROOT).toFile(); + // 保存到 world// 文件夹下,避免与其它 mod 冲突 + File modDir = new File(worldFolder, ExtendedAEPlus.MODID); + if (!modDir.exists()) { + modDir.mkdirs(); + } + this.saveFilePath = new File(modDir, FILE_NAME).toPath(); + if (Files.exists(this.saveFilePath)) { + CompoundTag root = NbtIo.readCompressed(this.saveFilePath.toFile()); + ListTag cellList = root.getList("list", Tag.TAG_COMPOUND); + for (int i = 0; i < cellList.size(); i++) { + CompoundTag cell = cellList.getCompound(i); + this.cells.put(cell.getUUID("uuid"), InfinityDataStorage.loadFromNBT(cell.getCompound("data"))); + } + } + } catch (IOException e) { + // 读取失败保持空,并打印栈追踪以便调试 + e.printStackTrace(); + } } /** - * 从 NBT 构造:用于在世界加载时从存档恢复数据 + * 保存当前内存数据到文件(会覆盖已有文件) */ - public InfinityStorageManager(CompoundTag nbt) { - ListTag cellList = nbt.getList("list", CompoundTag.TAG_COMPOUND); - for (int i = 0; i < cellList.size(); i++) { - CompoundTag cell = cellList.getCompound(i); - cells.put(cell.getUUID("uuid"), InfinityDataStorage.loadFromNBT(cell.getCompound("data"))); + public synchronized void saveToFile() { + if (this.saveFilePath == null) + return; + try { + CompoundTag root = new CompoundTag(); + ListTag cellList = new ListTag(); + for (Map.Entry entry : this.cells.entrySet()) { + CompoundTag cell = new CompoundTag(); + cell.putUUID("uuid", entry.getKey()); + cell.put("data", entry.getValue().serializeNBT()); + cellList.add(cell); + } + root.put("list", cellList); + // 使用压缩写入以节省空间 + NbtIo.writeCompressed(root, this.saveFilePath.toFile()); + } catch (IOException e) { + e.printStackTrace(); } - setDirty(); } - /** - * 根据给定的 ServerLevel 获取或创建该世界对应的 SavedData 实例并缓存到 INSTANCE - */ - public static InfinityStorageManager getForLevel(ServerLevel level) { - if (INSTANCE == null && level != null) { - INSTANCE = level.getDataStorage().computeIfAbsent(InfinityStorageManager::new, InfinityStorageManager::new, FILE_NAME); - } - return INSTANCE; - } - - @Override - public @NotNull CompoundTag save(@NotNull CompoundTag nbt) { - // 将内存中的所有 cell 序列化为一个 ListTag - ListTag cellList = new ListTag(); - for (Map.Entry entry : cells.entrySet()) { - CompoundTag cell = new CompoundTag(); - cell.putUUID("uuid", entry.getKey()); - cell.put("data", entry.getValue().serializeNBT()); - cellList.add(cell); - } - nbt.put("list", cellList); - return nbt; - } - - /** - * 更新或添加某个 UUID 对应的数据并标记为脏(需要保存) - */ public void updateCell(UUID uuid, InfinityDataStorage infinityDataStorage) { - cells.put(uuid, infinityDataStorage); - setDirty(); + this.cells.put(uuid, infinityDataStorage); + saveToFile(); } - /** - * 获取或创建某个 UUID 对应的数据容器 - */ public InfinityDataStorage getOrCreateCell(UUID uuid) { - if (!cells.containsKey(uuid)) { - updateCell(uuid, new InfinityDataStorage()); + if (!this.cells.containsKey(uuid)) { + InfinityDataStorage newCell = new InfinityDataStorage(); + this.cells.put(uuid, newCell); + saveToFile(); } - return cells.get(uuid); + return this.cells.get(uuid); } - /** - * 修改某个 UUID 对应的键与数量列表并保存(新的签名,stackAmounts 为 ListTag 字符串列表) - */ public void modifyCell(UUID cellID, ListTag stackKeys, ListTag stackAmounts) { InfinityDataStorage cellToModify = getOrCreateCell(cellID); if (stackKeys != null && stackAmounts != null) { @@ -104,11 +112,8 @@ public class InfinityStorageManager extends SavedData { updateCell(cellID, cellToModify); } - /** - * 删除某个 UUID 的持久化记录并标记为脏 - */ public void removeCell(UUID uuid) { - cells.remove(uuid); - setDirty(); + this.cells.remove(uuid); + saveToFile(); } } From ba22d3ae2e235c24842a21e3d5f7b9b4c51170e3 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Fri, 19 Sep 2025 19:21:06 +0800 Subject: [PATCH 02/34] =?UTF-8?q?=E8=B0=83=E6=95=B4mixin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- src/main/resources/extendedae_plus.mixins.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index b63a799..9c945b4 100644 --- a/build.gradle +++ b/build.gradle @@ -106,7 +106,7 @@ dependencies { //mae2 - modRuntimeOnly "curse.maven:modern-ae2-additions-1028068:6827727" +/* modRuntimeOnly "curse.maven:modern-ae2-additions-1028068:6827727"*/ modCompileOnly "curse.maven:modern-ae2-additions-1028068:6827727" //aea diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index a69afbb..a1e9d61 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -21,6 +21,7 @@ "ae2.client.gui.PatternProviderCloseMixin", "ae2.client.gui.PatternProviderScreenMixin", "ae2.client.gui.SlotGridLayoutMixin", + "ae2.menu.CraftConfirmMenuGoBackMixin", "extendedae.accessor.GuiExPatternTerminalAccessor", "extendedae.accessor.GuiExPatternTerminalSlotsRowAccessor", "extendedae.client.HighlightButtonMixin", @@ -57,7 +58,6 @@ "ae2.helpers.PatternProviderLogicAdvancedMixin", "ae2.helpers.PatternProviderLogicDoublingMixin", "ae2.menu.ContainerPatternEncodingTermMenuMixin", - "ae2.menu.CraftConfirmMenuGoBackMixin", "ae2.menu.MEStorageMenuMixin", "ae2.menu.PatternEncodingTermMenuMixin", "ae2.menu.PatternProviderMenuAdvancedMixin", From 0090d302f142df3c8321b8343c3126b3a9c56549 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Fri, 19 Sep 2025 20:17:32 +0800 Subject: [PATCH 03/34] =?UTF-8?q?=E8=A3=85=E9=85=8D=E7=9F=A9=E9=98=B5?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=A0=B8=E5=BF=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/extendedae_plus/ExtendedAEPlus.java | 8 ++++++ .../content/matrix/UploadCoreBlock.java | 26 +++++++++++++++++++ .../content/matrix/UploadCoreBlockEntity.java | 24 +++++++++++++++++ .../init/ModBlockEntities.java | 7 +++++ .../com/extendedae_plus/init/ModBlocks.java | 11 ++++++++ .../com/extendedae_plus/init/ModItems.java | 6 +++++ .../util/ExtendedAEPatternUploadUtil.java | 26 +++++++++++++++++-- 7 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlock.java create mode 100644 src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlockEntity.java diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index 25d675e..2688340 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -74,6 +74,14 @@ public class ExtendedAEPlus { ModNetwork.register(); // 注册自定义 Curios 宿主定位器,便于将菜单宿主信息在服务端与客户端间同步 MenuLocators.register(CuriosItemLocator.class, CuriosItemLocator::writeToPacket, CuriosItemLocator::readFromPacket); + + // 绑定装配矩阵上传核心的方块实体类型(延迟到注册完成后) + com.extendedae_plus.init.ModBlocks.ASSEMBLER_MATRIX_UPLOAD_CORE.get().setBlockEntity( + com.extendedae_plus.content.matrix.UploadCoreBlockEntity.class, + com.extendedae_plus.init.ModBlockEntities.UPLOAD_CORE_BE.get(), + null, + null + ); }); } diff --git a/src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlock.java b/src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlock.java new file mode 100644 index 0000000..9dcaddd --- /dev/null +++ b/src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlock.java @@ -0,0 +1,26 @@ +package com.extendedae_plus.content.matrix; + +import com.glodblock.github.extendedae.common.blocks.matrix.BlockAssemblerMatrixBase; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.state.BlockBehaviour; + +/** + * ExtendedAE_Plus: 装配矩阵上传核心方块(内部功能块)。 + * 仅用于作为多方块内部的“功能块”存在;是否允许自动上传由工具类检查集群中是否存在该核心决定。 + */ +public class UploadCoreBlock extends BlockAssemblerMatrixBase { + + public UploadCoreBlock() { + super(); + } + + public UploadCoreBlock(BlockBehaviour.Properties props) { + super(props); + } + + @Override + public Item getPresentItem() { + // 由对应的 BlockItem 注册返回,上传核心不需要特殊的 PresentItem,可返回自身的 BlockItem + return com.extendedae_plus.init.ModItems.ASSEMBLER_MATRIX_UPLOAD_CORE.get(); + } +} diff --git a/src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlockEntity.java b/src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlockEntity.java new file mode 100644 index 0000000..210b6ec --- /dev/null +++ b/src/main/java/com/extendedae_plus/content/matrix/UploadCoreBlockEntity.java @@ -0,0 +1,24 @@ +package com.extendedae_plus.content.matrix; + +import com.glodblock.github.extendedae.common.me.matrix.ClusterAssemblerMatrix; +import com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixFunction; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +/** + * ExtendedAE_Plus: 装配矩阵上传核心方块实体。 + * 作为矩阵内部功能块,仅用于标记该矩阵允许被自动上传(工具类会在集群中查找此实体)。 + */ +public class UploadCoreBlockEntity extends TileAssemblerMatrixFunction { + + public UploadCoreBlockEntity(BlockPos pos, BlockState state) { + super((BlockEntityType) com.extendedae_plus.init.ModBlockEntities.UPLOAD_CORE_BE.get(), pos, state); + } + + @Override + public void add(ClusterAssemblerMatrix c) { + // 无需修改集群,仅作为存在性标记。 + // 若后续需要限制为“最多一个”,可在 ExtendedAE_Plus 工具类或事件中做校验与提示。 + } +} diff --git a/src/main/java/com/extendedae_plus/init/ModBlockEntities.java b/src/main/java/com/extendedae_plus/init/ModBlockEntities.java index 7db517e..585d5ac 100644 --- a/src/main/java/com/extendedae_plus/init/ModBlockEntities.java +++ b/src/main/java/com/extendedae_plus/init/ModBlockEntities.java @@ -2,6 +2,7 @@ package com.extendedae_plus.init; import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.content.wireless.WirelessTransceiverBlockEntity; +import com.extendedae_plus.content.matrix.UploadCoreBlockEntity; import com.extendedae_plus.content.controller.NetworkPatternControllerBlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraftforge.registries.DeferredRegister; @@ -23,4 +24,10 @@ public final class ModBlockEntities { BLOCK_ENTITY_TYPES.register("network_pattern_controller", () -> BlockEntityType.Builder.of(NetworkPatternControllerBlockEntity::new, ModBlocks.NETWORK_PATTERN_CONTROLLER.get()).build(null)); + + // 装配矩阵上传核心 + public static final RegistryObject> UPLOAD_CORE_BE = + BLOCK_ENTITY_TYPES.register("assembler_matrix_upload_core", + () -> BlockEntityType.Builder.of(UploadCoreBlockEntity::new, + ModBlocks.ASSEMBLER_MATRIX_UPLOAD_CORE.get()).build(null)); } diff --git a/src/main/java/com/extendedae_plus/init/ModBlocks.java b/src/main/java/com/extendedae_plus/init/ModBlocks.java index 4316e2a..ae722b4 100644 --- a/src/main/java/com/extendedae_plus/init/ModBlocks.java +++ b/src/main/java/com/extendedae_plus/init/ModBlocks.java @@ -2,6 +2,7 @@ package com.extendedae_plus.init; import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.content.wireless.WirelessTransceiverBlock; +import com.extendedae_plus.content.matrix.UploadCoreBlock; import com.extendedae_plus.content.crafting.EPlusCraftingUnitType; import appeng.block.crafting.CraftingUnitBlock; import appeng.blockentity.crafting.CraftingBlockEntity; @@ -39,6 +40,16 @@ public final class ModBlocks { ) ); + // 装配矩阵上传核心(内部功能块) + public static final RegistryObject ASSEMBLER_MATRIX_UPLOAD_CORE = BLOCKS.register( + "assembler_matrix_upload_core", + () -> { + var b = new UploadCoreBlock(); + // 注意:方块实体绑定延后到 commonSetup 的 enqueueWork 中执行,避免注册阶段循环依赖 + return b; + } + ); + // Crafting Accelerators (reuse MAE2 textures/models) public static final RegistryObject ACCELERATOR_4x = BLOCKS.register( "4x_crafting_accelerator", diff --git a/src/main/java/com/extendedae_plus/init/ModItems.java b/src/main/java/com/extendedae_plus/init/ModItems.java index 6ec6a70..0f7b430 100644 --- a/src/main/java/com/extendedae_plus/init/ModItems.java +++ b/src/main/java/com/extendedae_plus/init/ModItems.java @@ -28,6 +28,12 @@ public final class ModItems { () -> new BlockItem(ModBlocks.NETWORK_PATTERN_CONTROLLER.get(), new Item.Properties()) ); + // 装配矩阵上传核心(方块物品) + public static final RegistryObject ASSEMBLER_MATRIX_UPLOAD_CORE = ITEMS.register( + "assembler_matrix_upload_core", + () -> new BlockItem(ModBlocks.ASSEMBLER_MATRIX_UPLOAD_CORE.get(), new Item.Properties()) + ); + // Crafting Accelerators public static final RegistryObject ACCELERATOR_4x = ITEMS.register( "4x_crafting_accelerator", diff --git a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java index d78af27..7911c9d 100644 --- a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java +++ b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java @@ -543,7 +543,7 @@ public class ExtendedAEPatternUploadUtil { var tiles = grid.getMachines(com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern.class); int idx = 0; for (com.glodblock.github.extendedae.common.tileentities.matrix.TileAssemblerMatrixPattern tile : tiles) { - if (tile != null && tile.isFormed() && tile.getMainNode().isActive()) { + if (tile != null && tile.isFormed() && tile.getMainNode().isActive() && clusterHasSingleUploadCore(tile)) { var inv = tile.getExposedInventory(); if (inv != null) { result.add(inv); @@ -565,7 +565,7 @@ public class ExtendedAEPatternUploadUtil { Set matrices = grid.getMachines(TileAssemblerMatrixBase.class); int idx = 0; for (TileAssemblerMatrixBase tile : matrices) { - if (tile != null && tile.isFormed()) { + if (tile != null && tile.isFormed() && clusterHasSingleUploadCore(tile)) { var capOpt = tile.getCapability(ForgeCapabilities.ITEM_HANDLER, null); if (capOpt != null) { var handler = capOpt.orElse(null); @@ -631,6 +631,28 @@ public class ExtendedAEPatternUploadUtil { return false; } + /** + * 判断给定矩阵集群中是否存在且仅存在一个“装配矩阵上传核心”。 + * 传入任意属于该集群的 Tile(如 Pattern/Crafter/Frame 等)。 + */ + private static boolean clusterHasSingleUploadCore(TileAssemblerMatrixBase any) { + try { + if (any == null || any.getCluster() == null) return false; + int cores = 0; + var it = any.getCluster().getBlockEntities(); + while (it.hasNext()) { + var te = it.next(); + if (te instanceof com.extendedae_plus.content.matrix.UploadCoreBlockEntity) { + cores++; + if (cores > 1) return false; // 至多一个 + } + } + return cores == 1; // 恰好一个 + } catch (Throwable t) { + return false; + } + } + /** * 检查当前菜单是否为ExtendedAE的扩展样板管理终端 * From 6e5b6b46f02094a6d9a77d800fbc3f4bad56944a Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Fri, 19 Sep 2025 20:40:33 +0800 Subject: [PATCH 04/34] =?UTF-8?q?=E8=A3=85=E9=85=8D=E7=9F=A9=E9=98=B5?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=A0=B8=E5=BF=83=E8=B4=B4=E5=9B=BE=E8=B5=84?= =?UTF-8?q?=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/ExtendedAEPatternUploadUtil.java | 6 +++--- .../blockstates/assembler_matrix_upload_core.json | 5 +++++ .../assets/extendedae_plus/lang/en_us.json | 4 +++- .../assets/extendedae_plus/lang/zh_cn.json | 9 ++++++++- .../block/assembler_matrix_upload_core.json | 6 ++++++ .../models/item/assembler_matrix_upload_core.json | 3 +++ .../block/assembler_matrix_upload_core.png | Bin 0 -> 225 bytes 7 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 src/main/resources/assets/extendedae_plus/blockstates/assembler_matrix_upload_core.json create mode 100644 src/main/resources/assets/extendedae_plus/models/block/assembler_matrix_upload_core.json create mode 100644 src/main/resources/assets/extendedae_plus/models/item/assembler_matrix_upload_core.json create mode 100644 src/main/resources/assets/extendedae_plus/textures/block/assembler_matrix_upload_core.png diff --git a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java index 7911c9d..d9f2542 100644 --- a/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java +++ b/src/main/java/com/extendedae_plus/util/ExtendedAEPatternUploadUtil.java @@ -632,7 +632,8 @@ public class ExtendedAEPatternUploadUtil { } /** - * 判断给定矩阵集群中是否存在且仅存在一个“装配矩阵上传核心”。 + * 判断给定矩阵集群中是否存在“装配矩阵上传核心”。 + * 要求:至少存在 1 个即可,不限制数量。 * 传入任意属于该集群的 Tile(如 Pattern/Crafter/Frame 等)。 */ private static boolean clusterHasSingleUploadCore(TileAssemblerMatrixBase any) { @@ -644,10 +645,9 @@ public class ExtendedAEPatternUploadUtil { var te = it.next(); if (te instanceof com.extendedae_plus.content.matrix.UploadCoreBlockEntity) { cores++; - if (cores > 1) return false; // 至多一个 } } - return cores == 1; // 恰好一个 + return cores >= 1; // 至少一个即可 } catch (Throwable t) { return false; } diff --git a/src/main/resources/assets/extendedae_plus/blockstates/assembler_matrix_upload_core.json b/src/main/resources/assets/extendedae_plus/blockstates/assembler_matrix_upload_core.json new file mode 100644 index 0000000..a099eb5 --- /dev/null +++ b/src/main/resources/assets/extendedae_plus/blockstates/assembler_matrix_upload_core.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "extendedae_plus:block/assembler_matrix_upload_core" } + } +} diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json index 2187081..320a792 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -35,6 +35,7 @@ "block.extendedae_plus.256x_crafting_accelerator": "256x Crafting Accelerator", "block.extendedae_plus.1024x_crafting_accelerator": "1024x Crafting Accelerator", "block.extendedae_plus.network_pattern_controller": "Pattern Supplier State Controller", + "block.extendedae_plus.assembler_matrix_upload_core": "Assembler Matrix Upload Core", "extendedae_plus.upload_to_matrix": "Upload to Assembly Matrix", "extendedae_plus.upload_to_matrix.success": "Pattern uploaded to the assembly matrix", @@ -85,5 +86,6 @@ "config.extendedae_plus.option.entityTickerCost": "Entity Ticker Base Energy Cost", "config.extendedae_plus.option.entityTickerBlackList": "Entity Ticker Blacklist", "config.extendedae_plus.option.entityTickerMultipliers": "Entity Ticker Extra Consumption Multipliers", - "config.extendedae_plus.option.craftingPauseThreshold": "AE synthesis calculation pause check threshold" + "config.extendedae_plus.option.craftingPauseThreshold": "AE synthesis calculation pause check threshold", + "block.extendedae_plus.assembler_matrix_upload_core": "Assembler Matrix Upload Core" } \ No newline at end of file diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index d89594e..d7996ab 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -35,6 +35,7 @@ "block.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元", "block.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元", "block.extendedae_plus.network_pattern_controller": "样板供应器状态控制器", + "block.extendedae_plus.assembler_matrix_upload_core": "装配矩阵上传核心", "extendedae_plus.upload_to_matrix": "上传到装配矩阵", "extendedae_plus.upload_to_matrix.success": "样板已上传到装配矩阵", @@ -85,5 +86,11 @@ "config.extendedae_plus.option.entityTickerCost": "实体加速器能量消耗基础值", "config.extendedae_plus.option.entityTickerBlackList": "实体加速器黑名单", "config.extendedae_plus.option.entityTickerMultipliers": "实体加速器额外消耗倍率", - "config.extendedae_plus.option.craftingPauseThreshold": "AE合成计算暂停检查阈值" + "config.extendedae_plus.option.craftingPauseThreshold": "AE合成计算暂停检查阈值", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip": "装配矩阵上传核心", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload": "上传到装配矩阵", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_success": "样板已上传到装配矩阵", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_not_crafting": "仅支持上传合成样板,处理样板将被忽略", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_no_matrix": "未在当前网络中找到已成型的装配矩阵", + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_full": "装配矩阵的样板仓已满或无法插入" } \ No newline at end of file diff --git a/src/main/resources/assets/extendedae_plus/models/block/assembler_matrix_upload_core.json b/src/main/resources/assets/extendedae_plus/models/block/assembler_matrix_upload_core.json new file mode 100644 index 0000000..768aff9 --- /dev/null +++ b/src/main/resources/assets/extendedae_plus/models/block/assembler_matrix_upload_core.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:block/cube_all", + "textures": { + "all": "extendedae_plus:block/assembler_matrix_upload_core" + } +} diff --git a/src/main/resources/assets/extendedae_plus/models/item/assembler_matrix_upload_core.json b/src/main/resources/assets/extendedae_plus/models/item/assembler_matrix_upload_core.json new file mode 100644 index 0000000..5470af1 --- /dev/null +++ b/src/main/resources/assets/extendedae_plus/models/item/assembler_matrix_upload_core.json @@ -0,0 +1,3 @@ +{ + "parent": "extendedae_plus:block/assembler_matrix_upload_core" +} diff --git a/src/main/resources/assets/extendedae_plus/textures/block/assembler_matrix_upload_core.png b/src/main/resources/assets/extendedae_plus/textures/block/assembler_matrix_upload_core.png new file mode 100644 index 0000000000000000000000000000000000000000..bac350b1ca8ac3c6423aaea69b4134ef3d5ea517 GIT binary patch literal 225 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|W_!9ghFJ6_ z??>IlPn_yFcrzm=Qqph*?}?feHY4$My`3uj z3MLC?c5y!7nsMO|1EbU4SwM6`>Bk(EE30QlOgS}Czj+z^ayB-$wx{*mU(cIxNb6{m z=9hIpb_J||mf5EMzH5E*DWiM4|Ib*`arj-Pk3*e}!XE}hX8%hK43qvIU|QHwF2wN7 X%K4y}yVzc!GZ{Qx{an^LB{Ts5t+`n7 literal 0 HcmV?d00001 From 1279031535f3726e7a2af4eea90ef3fdcf810e23 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Fri, 19 Sep 2025 20:49:13 +0800 Subject: [PATCH 05/34] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E6=97=A0?= =?UTF-8?q?=E9=99=90=E5=AD=98=E5=82=A8=E5=85=83=E4=BB=B6=E7=9A=84=E4=B8=B4?= =?UTF-8?q?=E6=97=B6=E6=96=87=E4=BB=B6=E5=86=99=E5=85=A5=20+=20=E5=8E=9F?= =?UTF-8?q?=E5=AD=90=E6=9B=BF=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/extendedae_plus/ExtendedAEPlus.java | 4 - .../InfinityBigIntegerCellInventory.java | 105 +++++++----------- .../util/storage/InfinityStorageManager.java | 65 +++++++---- 3 files changed, 83 insertions(+), 91 deletions(-) diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index ca77fbf..44a408a 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -3,7 +3,6 @@ package com.extendedae_plus; import appeng.api.storage.StorageCells; import appeng.menu.locator.MenuLocators; import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler; -import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory; import com.extendedae_plus.client.ClientRegistrar; import com.extendedae_plus.config.ModConfig; import com.extendedae_plus.init.*; @@ -53,9 +52,6 @@ public class ExtendedAEPlus { // 注册到Forge事件总线 MinecraftForge.EVENT_BUS.register(this); MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad); - // 注册每秒合并持久化队列的事件监听(Server tick end + stopping) - MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick); - MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerStopping); // 注册通用配置 ModConfig.init(); } diff --git a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java index d23c236..ce89043 100644 --- a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java +++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java @@ -17,14 +17,11 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; -import net.minecraftforge.event.TickEvent; -import net.minecraftforge.event.server.ServerStoppingEvent; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.text.DecimalFormat; -import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; @@ -48,8 +45,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 待持久化队列(用于 debounce:在服务器 tick 中合并持久化) private static final ConcurrentLinkedQueue PENDING_PERSIST = new ConcurrentLinkedQueue<>(); - // 用于按 tick 计数,每 20 tick(约 1 秒)触发一次合并写入 - private static int TICK_COUNTER = 0; // 数字格式化对象,保留两位小数(复用以减少对象分配) private static final DecimalFormat DF = new DecimalFormat("#.##"); @@ -137,13 +132,13 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 判断物品堆栈是否有UUID public boolean hasUUID() { - return this.stack.hasTag() && this.stack.getOrCreateTag().contains("uuid"); + return stack.hasTag() && stack.getOrCreateTag().contains("uuid"); } // 获取物品堆栈的UUID public UUID getUUID() { if (this.hasUUID()) { - return this.stack.getOrCreateTag().getUUID("uuid"); + return stack.getOrCreateTag().getUUID("uuid"); } else { return null; } @@ -151,17 +146,17 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 获取或初始化存储映射 private Object2ObjectMap getCellStoredMap() { - if (this.storedMap == null) { - this.storedMap = new Object2ObjectOpenHashMap<>(); + if (storedMap == null) { + storedMap = new Object2ObjectOpenHashMap<>(); this.loadCellStoredMap(); } - return this.storedMap; + return storedMap; } // 从存储中加载物品映射 private void loadCellStoredMap() { boolean corruptedTag = false; // 标记数据是否损坏 - if (!this.stack.hasTag()) + if (!stack.hasTag()) return; ListTag keys = this.getCellStorage().keys; ListTag amounts = this.getCellStorage().amounts; @@ -200,38 +195,12 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 标记数据需要保存,并通知容器或直接持久化 private void saveChanges() { // 标记为未持久化,交由容器或延迟任务合并写入以减少 I/O - this.isPersisted = false; - // 将本实例加入待处理队列(去重) - if (!PENDING_PERSIST.contains(this)) { - PENDING_PERSIST.add(this); - } - if (this.container != null) { - // 当存在容器时,仍然通知容器以便 AE2 在合并时回调 persist() - this.container.saveChanges(); - } - } - - /** - * 每个服务器 tick 调用一次,用于按秒合并并执行待持久化项的 persist() - */ - public static void onServerTick(TickEvent.ServerTickEvent event) { - if (event.phase != TickEvent.Phase.END) return; - TICK_COUNTER++; - if (TICK_COUNTER % 20 != 0) return; // 每 20 tick(约 1 秒)执行一次 - onServerStopping(null); - } - - /** - * 在服务器停止时强制刷新所有待持久化项 - */ - public static void onServerStopping(ServerStoppingEvent event) { - InfinityBigIntegerCellInventory inv; - while ((inv = PENDING_PERSIST.poll()) != null) { - try { - inv.persist(); - } catch (Exception e) { - e.printStackTrace(); - } + isPersisted = false; + if (container != null) { + // 当存在容器时,优先让容器统一处理持久化 + container.saveChanges(); + } else { + persist(); } } @@ -282,10 +251,10 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 如果存储为空,移除UUID和全局存储中的数据,并清理缓存的 types/total if (hasUUID()) { InfinityStorageManager.INSTANCE.removeCell(getUUID()); - if (this.stack.getTag() != null) { - this.stack.getTag().remove("uuid"); - this.stack.getTag().remove("types"); - this.stack.getTag().remove("total"); + if (stack.getTag() != null) { + stack.getTag().remove("uuid"); + stack.getTag().remove("types"); + stack.getTag().remove("total"); } initData(); } @@ -311,36 +280,34 @@ public class InfinityBigIntegerCellInventory implements StorageCell { if (keys.isEmpty()) { InfinityStorageManager.INSTANCE.updateCell(this.getUUID(), new InfinityDataStorage()); // 清理缓存 - if (this.stack.getTag() != null) { - this.stack.getTag().remove("types"); - this.stack.getTag().remove("total"); + if (stack.getTag() != null) { + stack.getTag().remove("types"); + stack.getTag().remove("total"); } } else { // amounts 现在为 CompoundTag 列表 InfinityStorageManager.INSTANCE.modifyCell(this.getUUID(), keys, amountTags); // 缓存类型数量与总量到 ItemStack 的 NBT,避免每次 tooltip 或展示时重新统计 try { - if (this.stack.getTag() == null) { - this.stack.setTag(new CompoundTag()); - } + if (stack.getTag() == null) stack.setTag(new CompoundTag()); int typesCount = keys.size(); - this.stack.getOrCreateTag().putInt("types", typesCount); - BigInteger total = BigInteger.ZERO; - for (Map.Entry e : map.object2ObjectEntrySet()) { - BigInteger v = e.getValue(); - if (v.compareTo(BigInteger.ZERO) > 0) { + stack.getOrCreateTag().putInt("types", typesCount); + java.math.BigInteger total = java.math.BigInteger.ZERO; + for (java.util.Map.Entry e : map.object2ObjectEntrySet()) { + java.math.BigInteger v = e.getValue(); + if (v.compareTo(java.math.BigInteger.ZERO) > 0) { total = total.add(v); } } - if (total.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) { - this.stack.getOrCreateTag().putLong("total", total.longValue()); + if (total.compareTo(java.math.BigInteger.valueOf(Long.MAX_VALUE)) <= 0) { + stack.getOrCreateTag().putLong("total", total.longValue()); } else { - this.stack.getOrCreateTag().putString("total", total.toString()); + stack.getOrCreateTag().putString("total", total.toString()); } } catch (Exception ignored) { } } - this.isPersisted = true; + isPersisted = true; } // 插入物品到存储单元 @@ -356,7 +323,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } // 如果没有UUID,生成UUID并初始化存储(延迟创建全局存储以避免在 manager 未就绪时 NPE) if (!this.hasUUID()) { - this.stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID()); + stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID()); InfinityStorageManager.INSTANCE.getOrCreateCell(getUUID()); // 确保 storedMap 初始化并从持久层加载数据 loadCellStoredMap(); @@ -403,4 +370,16 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } return 0; } + + // 获取存储单元内所有物品的总数量(格式化字符串) + public String getTotalStorage() { + // 使用缓存的 totalStored,避免每次全表扫描 + BigInteger total = BigInteger.ZERO; + for (BigInteger value : getCellStoredMap().values()) { + if (value.compareTo(BigInteger.ZERO) > 0) { + total = total.add(value); + } + } + return formatBigInteger(total); + } } diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java index 9aa2f64..26d880a 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -1,6 +1,5 @@ package com.extendedae_plus.util.storage; -import com.extendedae_plus.ExtendedAEPlus; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; @@ -13,13 +12,15 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.AtomicMoveNotSupportedException; import java.util.HashMap; import java.util.Map; import java.util.UUID; /** * InfinityStorageManager - *

+ * * 替代之前基于 SavedData 的实现,本类使用手动文件 I/O 在 world 目录下保存 NBT 数据, * 以避免依赖 Minecraft 的 SavedData 机制。 * 数据保持与之前兼容的 NBT 结构:根 Compound 包含 "list" => ListTag of Compound { uuid, data } @@ -28,16 +29,15 @@ public class InfinityStorageManager { public static final String FILE_NAME = "eap_infinity_biginteger_cells.dat"; - /** - * 全局单例,由 mod 在 world load 时初始化 - */ + /** 全局单例,由 mod 在 world load 时初始化 */ public static volatile InfinityStorageManager INSTANCE = new InfinityStorageManager(); private final Map cells = new HashMap<>(); private Path saveFilePath = null; - private InfinityStorageManager() {} + public InfinityStorageManager() { + } /** * 初始化并从 world 保存目录加载数据;若文件不存在则保持空状态 @@ -47,17 +47,15 @@ public class InfinityStorageManager { try { File worldFolder = serverLevel.getServer().getWorldPath(LevelResource.ROOT).toFile(); // 保存到 world// 文件夹下,避免与其它 mod 冲突 - File modDir = new File(worldFolder, ExtendedAEPlus.MODID); - if (!modDir.exists()) { - modDir.mkdirs(); - } - this.saveFilePath = new File(modDir, FILE_NAME).toPath(); - if (Files.exists(this.saveFilePath)) { - CompoundTag root = NbtIo.readCompressed(this.saveFilePath.toFile()); + File modDir = new File(worldFolder, "data"); + if (!modDir.exists()) modDir.mkdirs(); + saveFilePath = new File(modDir, FILE_NAME).toPath(); + if (Files.exists(saveFilePath)) { + CompoundTag root = NbtIo.readCompressed(saveFilePath.toFile()); ListTag cellList = root.getList("list", Tag.TAG_COMPOUND); for (int i = 0; i < cellList.size(); i++) { CompoundTag cell = cellList.getCompound(i); - this.cells.put(cell.getUUID("uuid"), InfinityDataStorage.loadFromNBT(cell.getCompound("data"))); + cells.put(cell.getUUID("uuid"), InfinityDataStorage.loadFromNBT(cell.getCompound("data"))); } } } catch (IOException e) { @@ -70,40 +68,58 @@ public class InfinityStorageManager { * 保存当前内存数据到文件(会覆盖已有文件) */ public synchronized void saveToFile() { - if (this.saveFilePath == null) - return; + if (saveFilePath == null) return; try { CompoundTag root = new CompoundTag(); ListTag cellList = new ListTag(); - for (Map.Entry entry : this.cells.entrySet()) { + for (Map.Entry entry : cells.entrySet()) { + // 跳过可能的 null key,防止写入时 NPE + if (entry.getKey() == null || entry.getValue() == null) continue; CompoundTag cell = new CompoundTag(); cell.putUUID("uuid", entry.getKey()); cell.put("data", entry.getValue().serializeNBT()); cellList.add(cell); } root.put("list", cellList); - // 使用压缩写入以节省空间 - NbtIo.writeCompressed(root, this.saveFilePath.toFile()); + // 使用压缩写入到临时文件,然后原子替换目标文件以避免半成品/0字节文件 + Path tmp = saveFilePath.resolveSibling(FILE_NAME + ".tmp"); + File tmpFile = tmp.toFile(); + // 确保临时文件的目录存在 + if (tmpFile.getParentFile() != null && !tmpFile.getParentFile().exists()) { + tmpFile.getParentFile().mkdirs(); + } + NbtIo.writeCompressed(root, tmpFile); + try { + Files.move(tmp, saveFilePath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException ex) { + // 若底层文件系统不支持原子移动,退回到非原子替换 + Files.move(tmp, saveFilePath, StandardCopyOption.REPLACE_EXISTING); + } } catch (IOException e) { e.printStackTrace(); } } public void updateCell(UUID uuid, InfinityDataStorage infinityDataStorage) { - this.cells.put(uuid, infinityDataStorage); + if (uuid == null) return; // 忽略无效 UUID + cells.put(uuid, infinityDataStorage); saveToFile(); } public InfinityDataStorage getOrCreateCell(UUID uuid) { - if (!this.cells.containsKey(uuid)) { + if (uuid == null) { + return InfinityDataStorage.EMPTY; + } + if (!cells.containsKey(uuid)) { InfinityDataStorage newCell = new InfinityDataStorage(); - this.cells.put(uuid, newCell); + cells.put(uuid, newCell); saveToFile(); } - return this.cells.get(uuid); + return cells.get(uuid); } public void modifyCell(UUID cellID, ListTag stackKeys, ListTag stackAmounts) { + if (cellID == null) return; InfinityDataStorage cellToModify = getOrCreateCell(cellID); if (stackKeys != null && stackAmounts != null) { cellToModify.keys = stackKeys; @@ -113,7 +129,8 @@ public class InfinityStorageManager { } public void removeCell(UUID uuid) { - this.cells.remove(uuid); + if (uuid == null) return; + cells.remove(uuid); saveToFile(); } } From 045b363c1b053e6326b2e457c36ac3cdacfb31e7 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Fri, 19 Sep 2025 21:25:37 +0800 Subject: [PATCH 06/34] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=85=8D=E6=96=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/extendedae_plus/init/ModCreativeTabs.java | 2 ++ .../recipes/assembler_matrix_upload_core.json | 11 +++++++++++ 2 files changed, 13 insertions(+) create mode 100644 src/main/resources/data/extendedae_plus/recipes/assembler_matrix_upload_core.json diff --git a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java index f30b2d6..293ac8e 100644 --- a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java +++ b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java @@ -21,6 +21,8 @@ public final class ModCreativeTabs { // 将本模组物品加入创造物品栏 output.accept(ModItems.WIRELESS_TRANSCEIVER.get()); output.accept(ModItems.NETWORK_PATTERN_CONTROLLER.get()); + // 装配矩阵上传核心 + output.accept(ModItems.ASSEMBLER_MATRIX_UPLOAD_CORE.get()); output.accept(ModItems.ACCELERATOR_4x.get()); output.accept(ModItems.ACCELERATOR_16x.get()); output.accept(ModItems.ACCELERATOR_64x.get()); diff --git a/src/main/resources/data/extendedae_plus/recipes/assembler_matrix_upload_core.json b/src/main/resources/data/extendedae_plus/recipes/assembler_matrix_upload_core.json new file mode 100644 index 0000000..12ca196 --- /dev/null +++ b/src/main/resources/data/extendedae_plus/recipes/assembler_matrix_upload_core.json @@ -0,0 +1,11 @@ +{ + "type": "minecraft:crafting_shapeless", + "ingredients": [ + { "item": "expatternprovider:assembler_matrix_wall" }, + { "item": "minecraft:lever" } + ], + "result": { + "item": "extendedae_plus:assembler_matrix_upload_core", + "count": 1 + } +} From ba79d0e31a885665a2380daef5ee6a0803ff9d0a Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Sat, 20 Sep 2025 02:32:11 +0800 Subject: [PATCH 07/34] =?UTF-8?q?fix:=20=E5=9B=9E=E9=80=80=E6=97=A0?= =?UTF-8?q?=E9=99=90=E7=A3=81=E7=9B=98=E4=BF=AE=E6=94=B9=E8=87=B3=E4=B8=8A?= =?UTF-8?q?=E4=B8=80=E7=89=88=E6=9C=AC=EF=BC=8C=E5=86=8D=E6=AC=A1=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=EF=BC=8C=E6=B7=BB=E5=8A=A0=E7=89=88=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InfinityBigIntegerCellInventory.java | 92 +++++++++++++++---- .../util/storage/InfinityDataStorage.java | 6 +- .../util/storage/InfinityStorageManager.java | 18 +++- 3 files changed, 94 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java index 2cbb417..e678dfc 100644 --- a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java +++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java @@ -16,6 +16,8 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.item.ItemStack; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.server.ServerStoppingEvent; @@ -23,7 +25,6 @@ import net.minecraftforge.event.server.ServerStoppingEvent; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; -import java.text.DecimalFormat; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; @@ -48,8 +49,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 待持久化队列(用于 debounce:在服务器 tick 中合并持久化) private static final ConcurrentLinkedQueue PENDING_PERSIST = new ConcurrentLinkedQueue<>(); - // 数字格式化对象,保留两位小数(复用以减少对象分配) - private static final DecimalFormat DF = new DecimalFormat("#.##"); + // 数字格式化对象:改为方法局部以避免静态非线程安全问题 // 关联的 ItemStack(含可能的 uuid NBT) private final ItemStack stack; @@ -106,6 +106,17 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 在服务器停止时被调用,立即强制持久化队列中的所有实例 public static void onServerStopping(ServerStoppingEvent event) { + // 尝试在服务端停止流程开始时初始化 SavedData(确保 INSTANCE 可用以便后续持久化) + try { + MinecraftServer server = event.getServer(); + if (server != null) { + for (ServerLevel level : server.getAllLevels()) { + InfinityStorageManager.getForLevel(level); + } + } + } catch (Throwable ignored) { + } + InfinityBigIntegerCellInventory inv; while ((inv = PENDING_PERSIST.poll()) != null) { try { @@ -116,7 +127,7 @@ public class InfinityBigIntegerCellInventory implements StorageCell { LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error1: {}", ignored.getMessage()); } } - // 额外尝试将全局存储管理器标记为脏以确保 SavedData 被写回(在单人模式下可能直接由系统触发) + // 将全局存储管理器标记为脏以确保 SavedData 被写回 try { var stor = getStorageInstance(); if (stor != null) stor.setDirty(); @@ -127,7 +138,8 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 将 BigInteger 格式化为带单位的字符串,保留两位小数 public static String formatBigInteger(BigInteger number) { - // 使用局部 DF(非线程安全),但 Minecraft 通常在主线程运行 + // 使用方法局部的 DecimalFormat,避免静态共享的非线程安全问题 + java.text.DecimalFormat df = new java.text.DecimalFormat("#.##"); BigDecimal bd = new BigDecimal(number); BigDecimal thousand = new BigDecimal(1000); String[] units = new String[]{"", "K", "M", "G", "T", "P", "E", "Z", "Y"}; @@ -139,17 +151,23 @@ public class InfinityBigIntegerCellInventory implements StorageCell { if (idx == 0) { return bd.setScale(0, RoundingMode.DOWN).toPlainString(); } - return DF.format(bd.doubleValue()) + units[idx]; + return df.format(bd.doubleValue()) + units[idx]; } // 获取当前存储单元的数据存储对象 private InfinityDataStorage getCellStorage() { if (this.getUUID() == null) { - // 如果没有UUID,返回空存储 - return InfinityDataStorage.EMPTY; + // 如果没有UUID,返回空存储(返回新实例以避免共享可变状态) + return InfinityDataStorage.empty(); } else { + // 在访问全局 SavedData 之前做防御性检查,避免在客户端或尚未初始化的情况下 NPE + InfinityStorageManager mgr = getStorageInstance(); + if (mgr == null) { + // SavedData 尚未就绪,视为空存储(返回新实例以避免共享可变状态) + return InfinityDataStorage.empty(); + } // 否则获取或创建对应UUID的存储 - return getStorageInstance().getOrCreateCell(getUUID()); + return mgr.getOrCreateCell(getUUID()); } } @@ -200,8 +218,9 @@ public class InfinityBigIntegerCellInventory implements StorageCell { private void loadCellStoredMap() { boolean corruptedTag = false; // 标记数据是否损坏 if (!stack.hasTag()) return; - ListTag keys = this.getCellStorage().keys; - ListTag amounts = this.getCellStorage().amounts; + InfinityDataStorage storage = this.getCellStorage(); + ListTag keys = storage.keys; + ListTag amounts = storage.amounts; int len = Math.min(keys.size(), amounts.size()); for (int i = 0; i < len; i++) { AEKey key = AEKey.fromTagGeneric(keys.getCompound(i)); @@ -233,6 +252,12 @@ public class InfinityBigIntegerCellInventory implements StorageCell { if (corruptedTag) { this.saveChanges(); } + // 打印加载后的摘要日志 + try { + var uuid = this.getUUID(); + LOGGER.info("Loaded cell {}: types={}, totalCached={}", uuid, this.getCellStoredMap().size(), this.getTotalStorage()); + } catch (Throwable ignored) { + } } // 标记数据需要保存,并通知容器或直接持久化 @@ -289,16 +314,32 @@ public class InfinityBigIntegerCellInventory implements StorageCell { public void persist() { if (this.isPersisted) return; + // 若全局存储管理器尚未就绪,重新入队并在未来 tick 重试 + InfinityStorageManager mgr = getStorageInstance(); + if (mgr == null) { + try { + var id = this.getUUID(); + LOGGER.info("Persist deferred for cell {}: storage manager not ready", id); + } catch (Throwable ignored) { + } + if (!PENDING_PERSIST.contains(this)) { + PENDING_PERSIST.offer(this); + } + return; + } + Object2ObjectMap map = this.getCellStoredMap(); if (map.isEmpty()) { // 如果存储为空,移除UUID和全局存储中的数据 if (this.hasUUID()) { - getStorageInstance().removeCell(getUUID()); + mgr.removeCell(getUUID()); if (stack.getTag() != null) { stack.getTag().remove("uuid"); // 移除缓存的 total 字段 stack.getTag().remove("total"); } + // 标记为已持久化,避免重复尝试 + isPersisted = true; } return; } @@ -320,10 +361,10 @@ public class InfinityBigIntegerCellInventory implements StorageCell { } // 如果没有Key,更新为空存储,否则保存数据 if (keys.isEmpty()) { - getStorageInstance().updateCell(this.getUUID(), new InfinityDataStorage()); + mgr.updateCell(this.getUUID(), new InfinityDataStorage()); } else { // amounts 现在为 CompoundTag 列表 - getStorageInstance().modifyCell(this.getUUID(), keys, amountTags); + mgr.modifyCell(this.getUUID(), keys, amountTags); } // 将缓存的 totalStored 同步到 ItemStack 的 NBT,优先使用 long if (stack.getOrCreateTag() != null) { @@ -337,6 +378,12 @@ public class InfinityBigIntegerCellInventory implements StorageCell { stack.getOrCreateTag().putInt("types", typesCount); } isPersisted = true; + // 打印持久化摘要日志 + try { + var uuid = this.getUUID(); + LOGGER.info("Persisted cell {}: types={}, totalCached={}", uuid, this.getCellStoredMap().size(), this.getTotalStorage()); + } catch (Throwable ignored) { + } } // 插入物品到存储单元 @@ -348,12 +395,19 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 不允许存储无限单元自身 if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) return 0; - // 如果没有UUID,生成UUID并初始化存储 + // 如果没有UUID,尝试在服务器端且存储管理器已就绪时生成UUID并初始化存储 if (!this.hasUUID()) { - stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID()); - getStorageInstance().getOrCreateCell(getUUID()); - // 确保 storedMap 初始化并从持久层加载数据 - this.getCellStoredMap(); + InfinityStorageManager mgr = getStorageInstance(); + // 仅在 SavedData 已就绪(通常在服务器端)时创建 UUID 并注册持久化存储 + if (mgr != null) { + stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID()); + mgr.getOrCreateCell(getUUID()); + // 确保 storedMap 初始化并从持久层加载数据 + this.getCellStoredMap(); + } else { + // SavedData 未就绪:把插入作为本地内存操作(不生成 UUID,不持久化),确保 storedMap 初始化以容纳临时数据 + this.getCellStoredMap(); + } } Object2ObjectMap map = this.getCellStoredMap(); BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO); diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java index 4ef9036..c56e421 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java @@ -18,8 +18,10 @@ import net.minecraft.nbt.Tag; */ public class InfinityDataStorage { - /** 空实例(表示没有数据) */ - public static final InfinityDataStorage EMPTY = new InfinityDataStorage(); + /** 空实例的访问器(返回新实例以避免共享可变状态) */ + public static InfinityDataStorage empty() { + return new InfinityDataStorage(); + } /** 序列化的键列表(NBT ListTag,元素为 CompoundTag) */ public ListTag keys; diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java index e24a5ab..f6a31a9 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -10,6 +10,8 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; + /** * InfinityStorageManager *

@@ -21,6 +23,10 @@ import java.util.UUID; */ public class InfinityStorageManager extends SavedData { + // 当前磁盘格式版本号,增加字段用于向后/向前兼容 + private static final int FORMAT_VERSION = 1; + + /** * SavedData 文件名常量 */ @@ -42,10 +48,18 @@ public class InfinityStorageManager extends SavedData { * 从 NBT 构造:用于在世界加载时从存档恢复数据 */ public InfinityStorageManager(CompoundTag nbt) { + // 读取格式版本,缺省视为 1(兼容旧档) + int version = nbt.contains("format_version") ? nbt.getInt("format_version") : 1; + LOGGER.info("Loading InfinityStorageManager format_version={}", version); + ListTag cellList = nbt.getList("list", CompoundTag.TAG_COMPOUND); for (int i = 0; i < cellList.size(); i++) { CompoundTag cell = cellList.getCompound(i); - cells.put(cell.getUUID("uuid"), InfinityDataStorage.loadFromNBT(cell.getCompound("data"))); + java.util.UUID uuid = cell.getUUID("uuid"); + CompoundTag dataTag = cell.getCompound("data"); + InfinityDataStorage data = InfinityDataStorage.loadFromNBT(dataTag); + cells.put(uuid, data); + LOGGER.info("Loaded InfinityDataStorage for uuid {}: keys={}, amounts={}", uuid, data.keys.size(), data.amounts.size()); } setDirty(); } @@ -71,6 +85,8 @@ public class InfinityStorageManager extends SavedData { cellList.add(cell); } nbt.put("list", cellList); + // 写入当前格式版本号,便于未来迁移与兼容判断 + nbt.putInt("format_version", FORMAT_VERSION); return nbt; } From bc3b280fdb88eef2d2a528854344b57f4b0173c5 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Sat, 20 Sep 2025 10:11:36 +0800 Subject: [PATCH 08/34] =?UTF-8?q?fix:=20=E5=A2=9E=E5=8A=A0=E5=AF=B9?= =?UTF-8?q?=E5=89=8Dfix=E7=89=88=E6=9C=ACdat=E7=9A=84=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/storage/InfinityStorageManager.java | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java index f6a31a9..24bbe28 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -2,10 +2,18 @@ package com.extendedae_plus.util.storage; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtIo; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.saveddata.SavedData; +import net.minecraft.world.level.storage.LevelResource; import org.jetbrains.annotations.NotNull; +import java.io.File; +import java.io.IOException; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -50,7 +58,14 @@ public class InfinityStorageManager extends SavedData { public InfinityStorageManager(CompoundTag nbt) { // 读取格式版本,缺省视为 1(兼容旧档) int version = nbt.contains("format_version") ? nbt.getInt("format_version") : 1; - LOGGER.info("Loading InfinityStorageManager format_version={}", version); + if (!nbt.contains("format_version")) { + // 旧档未包含 format_version,标记为需要写回以便下次保存写入版本号 + LOGGER.info("Found legacy InfinityStorageManager dat without format_version; treating as version {} and marking dirty for upgrade", version); + // 立即标记为脏以触发尽快写盘(升级写入 format_version) + this.setDirty(); + } else { + LOGGER.info("Loading InfinityStorageManager format_version={}", version); + } ListTag cellList = nbt.getList("list", CompoundTag.TAG_COMPOUND); for (int i = 0; i < cellList.size(); i++) { @@ -69,6 +84,53 @@ public class InfinityStorageManager extends SavedData { */ public static InfinityStorageManager getForLevel(ServerLevel level) { if (INSTANCE == null && level != null) { + // 迁移逻辑:若旧的压缩 dat 文件存在且缺少 format_version,则读取并补上 version,备份原文件 + try { + Path dataDir = level.getServer().getWorldPath(new LevelResource("data")); + Path filePath = dataDir.resolve(FILE_NAME + ".dat"); + File oldFile = filePath.toFile(); + if (oldFile.exists()) { + // 检测是否为 gzip(压缩 NBT) + boolean isGzip = false; + try (var fis = Files.newInputStream(filePath)) { + int b1 = fis.read(); + int b2 = fis.read(); + isGzip = (b1 == 0x1F && b2 == 0x8B); + } catch (IOException ignored) { + } + if (isGzip) { + try { + var root = NbtIo.readCompressed(oldFile); + if (!root.contains("format_version")) { + // 备份旧文件 + Path bak = filePath.resolveSibling(FILE_NAME + ".dat.bak"); + try { + Files.copy(filePath, bak); + } catch (IOException ignored) { + } + // 写回并加入 format_version + root.putInt("format_version", FORMAT_VERSION); + Path tmp = filePath.resolveSibling(FILE_NAME + ".tmp"); + File tmpFile = tmp.toFile(); + if (tmpFile.getParentFile() != null && !tmpFile.getParentFile().exists()) { + tmpFile.getParentFile().mkdirs(); + } + NbtIo.writeCompressed(root, tmpFile); + try { + Files.move(tmp, filePath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException ex) { + Files.move(tmp, filePath, StandardCopyOption.REPLACE_EXISTING); + } + LOGGER.info("Migrated old compressed {} to include format_version", filePath); + } + } catch (IOException ex) { + LOGGER.info("Failed to migrate old compressed file {}: {}", filePath, ex.getMessage()); + } + } + } + } catch (Throwable ignored) { + } + INSTANCE = level.getDataStorage().computeIfAbsent(InfinityStorageManager::new, InfinityStorageManager::new, FILE_NAME); } return INSTANCE; From b812dac9deedb17d5a4656a12161e67e68533171 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Sat, 20 Sep 2025 14:10:23 +0800 Subject: [PATCH 09/34] fix --- src/main/java/com/extendedae_plus/ExtendedAEPlus.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index 23b2770..9d993f8 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -124,7 +124,7 @@ public class ExtendedAEPlus { private static void onLevelLoad(LevelEvent.Load event) { if (event.getLevel() instanceof ServerLevel serverLevel) { // 初始化自定义的文件 I/O 存储管理器 - InfinityStorageManager.INSTANCE.initFromWorld(serverLevel); + InfinityStorageManager.getForLevel(serverLevel); } } } From 39f89183e737d0a1d567c4524a26b99e970ed2ef Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Sat, 20 Sep 2025 15:09:38 +0800 Subject: [PATCH 10/34] =?UTF-8?q?=E4=BF=AE=E5=A4=8Djei=E4=B8=8D=E5=AD=98?= =?UTF-8?q?=E5=9C=A8=E6=97=B6classnotfonud=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../extendedae_plus/client/InputEvents.java | 148 +++++++++++------- 1 file changed, 88 insertions(+), 60 deletions(-) diff --git a/src/main/java/com/extendedae_plus/client/InputEvents.java b/src/main/java/com/extendedae_plus/client/InputEvents.java index 50ee7b2..6a921a4 100644 --- a/src/main/java/com/extendedae_plus/client/InputEvents.java +++ b/src/main/java/com/extendedae_plus/client/InputEvents.java @@ -18,6 +18,7 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.ScreenEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.ModList; import org.lwjgl.glfw.GLFW; import java.util.Optional; @@ -28,88 +29,115 @@ public final class InputEvents { @SubscribeEvent public static void onMouseButtonPre(ScreenEvent.MouseButtonPressed.Pre event) { + // 若未安装 JEI,直接跳过,避免触发 JEI 类加载导致的 NoClassDefFoundError + if (!ModList.get().isLoaded("jei")) { + return; + } + // 若 JEI 运行时尚未就绪,跳过 + if (JeiRuntimeProxy.get() == null) { + return; + } // 优先处理:Shift + 左键(拉取或下单) if (event.getButton() == GLFW.GLFW_MOUSE_BUTTON_LEFT && Screen.hasShiftDown()) { - double mouseX = event.getMouseX(); - double mouseY = event.getMouseY(); - Optional> hovered = JeiRuntimeProxy.getIngredientUnderMouse(mouseX, mouseY); - if (hovered.isEmpty()) { - hovered = JeiRuntimeProxy.getIngredientUnderMouse(); - } - if (hovered.isPresent()) { - // 若 JEI 作弊模式开启,则放行给 JEI 处理(Shift+左键=一组) - if (JeiRuntimeProxy.isJeiCheatModeEnabled()) { - return; + try { + double mouseX = event.getMouseX(); + double mouseY = event.getMouseY(); + Optional> hovered = JeiRuntimeProxy.getIngredientUnderMouse(mouseX, mouseY); + if (hovered.isEmpty()) { + hovered = JeiRuntimeProxy.getIngredientUnderMouse(); } - ITypedIngredient typed = hovered.get(); - GenericStack stack = GenericEntryStackHelper.ingredientToStack(typed); - if (stack != null) { - // 发送到服务端:若网络有库存则拉取一组到空槽,否则若可合成则打开下单界面 - ModNetwork.CHANNEL.sendToServer(new PullFromJeiOrCraftC2SPacket(stack)); - // 消费此次点击,避免 JEI/原版对左键的其它处理 - event.setCanceled(true); - return; + if (hovered.isPresent()) { + // 若 JEI 作弊模式开启,则放行给 JEI 处理(Shift+左键=一组) + if (JeiRuntimeProxy.isJeiCheatModeEnabled()) { + return; + } + ITypedIngredient typed = hovered.get(); + GenericStack stack = GenericEntryStackHelper.ingredientToStack(typed); + if (stack != null) { + // 发送到服务端:若网络有库存则拉取一组到空槽,否则若可合成则打开下单界面 + ModNetwork.CHANNEL.sendToServer(new PullFromJeiOrCraftC2SPacket(stack)); + // 消费此次点击,避免 JEI/原版对左键的其它处理 + event.setCanceled(true); + return; + } } + } catch (Throwable ignored) { + // 兼容 JEI 版本差异或运行时异常 } } // 中键:打开 AE 下单界面(保持原有功能) if (event.getButton() == GLFW.GLFW_MOUSE_BUTTON_MIDDLE) { - // 优先在 JEI 配方界面基于坐标获取;若无,再从覆盖层/书签获取 - double mouseX = event.getMouseX(); - double mouseY = event.getMouseY(); - Optional> hovered = JeiRuntimeProxy.getIngredientUnderMouse(mouseX, mouseY); - if (hovered.isEmpty()) { - hovered = JeiRuntimeProxy.getIngredientUnderMouse(); + try { + // 优先在 JEI 配方界面基于坐标获取;若无,再从覆盖层/书签获取 + double mouseX = event.getMouseX(); + double mouseY = event.getMouseY(); + Optional> hovered = JeiRuntimeProxy.getIngredientUnderMouse(mouseX, mouseY); + if (hovered.isEmpty()) { + hovered = JeiRuntimeProxy.getIngredientUnderMouse(); + } + if (hovered.isEmpty()) return; + + ITypedIngredient typed = hovered.get(); + // 若 JEI 作弊模式开启,则放行给 JEI 处理(中键=一组) + if (JeiRuntimeProxy.isJeiCheatModeEnabled()) { + return; + } + GenericStack stack = GenericEntryStackHelper.ingredientToStack(typed); + if (stack == null) return; + + // 发送到服务端,让其验证并打开 CraftAmountMenu + ModNetwork.CHANNEL.sendToServer(new OpenCraftFromJeiC2SPacket(stack)); + + // 消费此次点击,避免 JEI/原版对中键的其它处理 + event.setCanceled(true); + } catch (Throwable ignored) { + // 兼容 JEI 版本差异或运行时异常 } - if (hovered.isEmpty()) return; - - ITypedIngredient typed = hovered.get(); - // 若 JEI 作弊模式开启,则放行给 JEI 处理(中键=一组) - if (JeiRuntimeProxy.isJeiCheatModeEnabled()) { - return; - } - GenericStack stack = GenericEntryStackHelper.ingredientToStack(typed); - if (stack == null) return; - - // 发送到服务端,让其验证并打开 CraftAmountMenu - ModNetwork.CHANNEL.sendToServer(new OpenCraftFromJeiC2SPacket(stack)); - - // 消费此次点击,避免 JEI/原版对中键的其它处理 - event.setCanceled(true); } } @SubscribeEvent public static void onKeyPressedPre(ScreenEvent.KeyPressed.Pre event) { + // 若未安装 JEI,直接跳过 + if (!ModList.get().isLoaded("jei")) { + return; + } + if (JeiRuntimeProxy.get() == null) { + return; + } if (event.getKeyCode() != GLFW.GLFW_KEY_F) return; // 仅当鼠标确实悬停在 JEI 配料上时触发 - Optional> hovered = JeiRuntimeProxy.getIngredientUnderMouse(); - if (hovered.isEmpty()) return; + try { + Optional> hovered = JeiRuntimeProxy.getIngredientUnderMouse(); + if (hovered.isEmpty()) return; - ITypedIngredient typed = hovered.get(); + ITypedIngredient typed = hovered.get(); - // 通用获取显示名称(兼容物品/流体等) - String name = JeiRuntimeProxy.getTypedIngredientDisplayName(typed); - if (name == null || name.isEmpty()) return; + // 通用获取显示名称(兼容物品/流体等) + String name = JeiRuntimeProxy.getTypedIngredientDisplayName(typed); + if (name == null || name.isEmpty()) return; - // 写入 AE2 终端的搜索框 - var screen = Minecraft.getInstance().screen; - if (screen instanceof MEStorageScreen me) { - try { - MEStorageScreenAccessor acc = (MEStorageScreenAccessor) me; - acc.eap$getSearchField().setValue(name); - acc.eap$setSearchText(name); // 同步到 Repo 并刷新 - event.setCanceled(true); - } catch (Throwable ignored) { + // 写入 AE2 终端的搜索框 + var screen = Minecraft.getInstance().screen; + if (screen instanceof MEStorageScreen me) { + try { + MEStorageScreenAccessor acc = (MEStorageScreenAccessor) me; + acc.eap$getSearchField().setValue(name); + acc.eap$setSearchText(name); // 同步到 Repo 并刷新 + event.setCanceled(true); + } catch (Throwable ignored) { + } + }else if (screen instanceof GuiExPatternTerminal gpt) { + try { + GuiExPatternTerminalAccessor acc = (GuiExPatternTerminalAccessor) gpt; + acc.getSearchOutField().setValue(name); + event.setCanceled(true); + }catch (Throwable ignored) {} } - }else if (screen instanceof GuiExPatternTerminal gpt) { - try { - GuiExPatternTerminalAccessor acc = (GuiExPatternTerminalAccessor) gpt; - acc.getSearchOutField().setValue(name); - event.setCanceled(true); - }catch (Throwable ignored) {} + } catch (Throwable ignored) { + // 兼容 JEI 版本差异或运行时异常 } } } From d6ac6e8045dcb54a61f478ab61d0cfab84df526c Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Sat, 20 Sep 2025 16:29:46 +0800 Subject: [PATCH 11/34] =?UTF-8?q?fix:=20=E8=BF=9B=E4=B8=80=E6=AD=A5?= =?UTF-8?q?=E7=A1=AE=E4=BF=9D=E6=B8=B8=E6=88=8F=E5=9C=A8=E7=89=B9=E6=AE=8A?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E6=83=85=E5=86=B5=E4=B8=8B=E8=83=BD=E6=AD=A3?= =?UTF-8?q?=E7=A1=AE=E4=BF=9D=E5=AD=98=E6=95=B0=E6=8D=AE=EF=BC=8C=E5=A6=82?= =?UTF-8?q?Alt=20F4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 2 +- .../InfinityBigIntegerCellInventory.java | 144 ++++++++++-------- .../util/storage/InfinityStorageManager.java | 19 +++ 3 files changed, 104 insertions(+), 61 deletions(-) diff --git a/gradle.properties b/gradle.properties index 32815b9..2c5731e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G loom.platform = forge # Mod properties -mod_version = 1.4.2-fix +mod_version = 1.4.2-fix1 maven_group = com.extendedae_plus archives_name = extendedae_plus diff --git a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java index e678dfc..efbf16b 100644 --- a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java +++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java @@ -21,6 +21,7 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.world.item.ItemStack; import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.server.ServerStoppingEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; import java.math.BigDecimal; import java.math.BigInteger; @@ -88,54 +89,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell { return InfinityStorageManager.INSTANCE; } - // 服务器 tick 回调:合并并执行待持久化项 - public static void onServerTick(TickEvent.ServerTickEvent event) { - if (event.phase != TickEvent.Phase.END) return; - InfinityBigIntegerCellInventory inv; - // 处理本次 tick 中的全部待持久化项 - while ((inv = PENDING_PERSIST.poll()) != null) { - try { - if (!inv.isPersisted) { - inv.persist(); - } - } catch (Throwable ignored) { - LOGGER.info("InfinityBigIntegerCellInventory onServerTick error: {}", ignored.getMessage()); - } - } - } - - // 在服务器停止时被调用,立即强制持久化队列中的所有实例 - public static void onServerStopping(ServerStoppingEvent event) { - // 尝试在服务端停止流程开始时初始化 SavedData(确保 INSTANCE 可用以便后续持久化) - try { - MinecraftServer server = event.getServer(); - if (server != null) { - for (ServerLevel level : server.getAllLevels()) { - InfinityStorageManager.getForLevel(level); - } - } - } catch (Throwable ignored) { - } - - InfinityBigIntegerCellInventory inv; - while ((inv = PENDING_PERSIST.poll()) != null) { - try { - if (!inv.isPersisted) { - inv.persist(); - } - } catch (Throwable ignored) { - LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error1: {}", ignored.getMessage()); - } - } - // 将全局存储管理器标记为脏以确保 SavedData 被写回 - try { - var stor = getStorageInstance(); - if (stor != null) stor.setDirty(); - } catch (Throwable ignored) { - LOGGER.info("InfinityBigIntegerCellInventory onServerStopping error2: {}", ignored.getMessage()); - } - } - // 将 BigInteger 格式化为带单位的字符串,保留两位小数 public static String formatBigInteger(BigInteger number) { // 使用方法局部的 DecimalFormat,避免静态共享的非线程安全问题 @@ -252,12 +205,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell { if (corruptedTag) { this.saveChanges(); } - // 打印加载后的摘要日志 - try { - var uuid = this.getUUID(); - LOGGER.info("Loaded cell {}: types={}, totalCached={}", uuid, this.getCellStoredMap().size(), this.getTotalStorage()); - } catch (Throwable ignored) { - } } // 标记数据需要保存,并通知容器或直接持久化 @@ -378,12 +325,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell { stack.getOrCreateTag().putInt("types", typesCount); } isPersisted = true; - // 打印持久化摘要日志 - try { - var uuid = this.getUUID(); - LOGGER.info("Persisted cell {}: types={}, totalCached={}", uuid, this.getCellStoredMap().size(), this.getTotalStorage()); - } catch (Throwable ignored) { - } } // 插入物品到存储单元 @@ -464,4 +405,87 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 使用缓存的 totalStored,避免每次全表扫描 return formatBigInteger(totalStored); } + + /** 定时 tick 保存计数器 */ + private static int SAVE_TICK_COUNTER = 0; + + /** tick 间隔,单位为服务器 tick(20 tick ≈ 1 秒) */ + private static final int SAVE_TICK_INTERVAL = 20; // 约30秒 + + /** 服务器 tick 事件:处理队列并定时保存 */ + @SubscribeEvent + public static void onServerTick(TickEvent.ServerTickEvent event) { + if (event.phase != TickEvent.Phase.END) return; + + // 处理待持久化队列 + InfinityBigIntegerCellInventory inv; + while ((inv = PENDING_PERSIST.poll()) != null) { + try { + if (!inv.isPersisted) { + inv.persist(); + } + } catch (Throwable ex) { + LOGGER.info("InfinityBigIntegerCellInventory onServerTick error: {}", ex.getMessage()); + } + } + + // 定时标记全局存储为脏 + try { + if (++SAVE_TICK_COUNTER >= SAVE_TICK_INTERVAL) { + SAVE_TICK_COUNTER = 0; + if (InfinityStorageManager.INSTANCE != null) { + InfinityStorageManager.INSTANCE.setDirty(); + } + } + } catch (Throwable ex) { + LOGGER.info("InfinityBigIntegerCellInventory tick dirty set error: {}", ex.getMessage()); + } + } + + // 在服务器停止时被调用,立即强制持久化队列中的所有实例 + @SubscribeEvent + public static void onServerStopping(ServerStoppingEvent event) { + // 尝试在服务端停止流程开始时初始化 SavedData(确保 INSTANCE 可用以便后续持久化) + try { + MinecraftServer server = event.getServer(); + if (server != null) { + for (ServerLevel level : server.getAllLevels()) { + InfinityStorageManager.getForLevel(level); + } + } + } catch (Throwable ignored) { + LOGGER.info("InfinityBigIntegerCellInventory onServerStopping init error: {}", ignored.getMessage()); + } + + InfinityBigIntegerCellInventory inv; + while ((inv = PENDING_PERSIST.poll()) != null) { + try { + if (!inv.isPersisted) { + inv.persist(); + } + } catch (Throwable ex) { + LOGGER.info("InfinityBigIntegerCellInventory onServerStopping persist error: {}", ex.getMessage()); } + } + + // 强制写回磁盘,确保直接关闭游戏也能保存 + try { + InfinityStorageManager mgr = InfinityStorageManager.INSTANCE; + if (mgr != null) { + MinecraftServer server = event.getServer(); + if (server != null) { + for (ServerLevel level : server.getAllLevels()) { + try { + mgr.forceSaveAll(level); + LOGGER.info("Stop forceSaveAll for level {}", level.dimension().location()); + } catch (Throwable ex) { + LOGGER.info("InfinityBigIntegerCellInventory forceSaveAll error for level {}: {}", + level.dimension().location(), ex.getMessage()); + } + } + } + } + } catch (Throwable ex) { + LOGGER.info("InfinityBigIntegerCellInventory onServerStopping forceSaveAll error: {}", ex.getMessage()); + } + } } diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java index 24bbe28..5f8f3fe 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -189,4 +189,23 @@ public class InfinityStorageManager extends SavedData { cells.remove(uuid); setDirty(); } + + /** + * 强制将内存中的所有 InfinityDataStorage 写入磁盘 + * 可在服务器停止或 tick 中调用,确保数据不会丢失 + */ + public void forceSaveAll(ServerLevel level) { + try { + if (level != null) { + this.setDirty(); + CompoundTag nbt = new CompoundTag(); + this.save(nbt); + File file = level.getServer().getWorldPath(new LevelResource("data")) + .resolve(FILE_NAME + ".dat").toFile(); + NbtIo.writeCompressed(nbt, file); + } + } catch (Throwable ex) { + LOGGER.info("InfinityStorageManager forceSaveAll error: {}", ex.getMessage()); + } + } } From 55628250bcccfb9f7aa8a63810177af0b711889f Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Sat, 20 Sep 2025 19:28:11 +0800 Subject: [PATCH 12/34] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=8C=87?= =?UTF-8?q?=E4=BB=A4=E7=94=A8=E4=BA=8E=E8=8E=B7=E5=8F=96=E5=AD=98=E6=A1=A3?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E7=9A=84=E5=85=A8=E9=83=A8=E6=97=A0=E9=99=90?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E5=85=83=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 2 +- .../com/extendedae_plus/ExtendedAEPlus.java | 8 +++ .../ae/items/InfinityBigIntegerCellItem.java | 14 +++++ .../command/InfinityDiskGiveCommand.java | 61 +++++++++++++++++++ .../util/storage/InfinityStorageManager.java | 20 ++++++ 5 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/extendedae_plus/command/InfinityDiskGiveCommand.java diff --git a/gradle.properties b/gradle.properties index 2c5731e..b86c90f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G loom.platform = forge # Mod properties -mod_version = 1.4.2-fix1 +mod_version = 1.4.2-fix2 maven_group = com.extendedae_plus archives_name = extendedae_plus diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index 25d675e..3e02a54 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -17,6 +17,8 @@ import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.level.LevelEvent; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.event.RegisterCommandsEvent; +import com.extendedae_plus.command.InfinityDiskGiveCommand; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; @@ -53,6 +55,8 @@ public class ExtendedAEPlus { // 注册到Forge事件总线 MinecraftForge.EVENT_BUS.register(this); MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad); + // 注册命令注册监听 + MinecraftForge.EVENT_BUS.addListener(this::onRegisterCommands); // 注册通用配置 ModConfig.init(); // 注册 InfinityBigIntegerCellInventory 的事件监听(tick flush 与停止时 flush) @@ -123,4 +127,8 @@ public class ExtendedAEPlus { InfinityStorageManager.getForLevel(serverLevel); } } + + private void onRegisterCommands(RegisterCommandsEvent event) { + InfinityDiskGiveCommand.register(event.getDispatcher()); + } } diff --git a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java index dde5b96..75baebf 100644 --- a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java +++ b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java @@ -7,15 +7,18 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.LongTag; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.level.Level; +import net.minecraftforge.registries.ForgeRegistries; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; import java.math.BigInteger; import java.util.List; +import java.util.Objects; public class InfinityBigIntegerCellItem extends Item { @@ -79,4 +82,15 @@ public class InfinityBigIntegerCellItem extends Item { } } } + + /** + * 创建一个带有指定 UUID 的 Infinity 磁盘 ItemStack + */ + public static ItemStack withUUID(java.util.UUID uuid) { + ItemStack stack = new ItemStack(Objects.requireNonNull( + ForgeRegistries.ITEMS.getValue(new ResourceLocation("extendedae_plus", "infinity_biginteger_cell") + ))); + stack.getOrCreateTag().putUUID("uuid", uuid); + return stack; + } } \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/command/InfinityDiskGiveCommand.java b/src/main/java/com/extendedae_plus/command/InfinityDiskGiveCommand.java new file mode 100644 index 0000000..306e483 --- /dev/null +++ b/src/main/java/com/extendedae_plus/command/InfinityDiskGiveCommand.java @@ -0,0 +1,61 @@ +package com.extendedae_plus.command; + +import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem; +import com.extendedae_plus.util.storage.InfinityStorageManager; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.context.CommandContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; + +import java.util.UUID; + +/** + * /eap give_infinity_disks + * 为执行命令的玩家生成当前世界已加载的所有 Infinity 磁盘(按 UUID)并发放到玩家物品栏 + */ +public class InfinityDiskGiveCommand { + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("eap").then( + Commands.literal("give_infinity_disks").executes(InfinityDiskGiveCommand::execute) + )); + } + + private static int execute(CommandContext ctx) { + CommandSourceStack source = ctx.getSource(); + try { + ServerPlayer player = source.getPlayerOrException(); + if (player.level() == null || !(player.level() instanceof ServerLevel)) { + source.sendFailure(Component.literal("This command must be run on server side.")); + return 0; + } + InfinityStorageManager mgr = InfinityStorageManager.getForLevel((ServerLevel) player.level()); + if (mgr == null) { + source.sendFailure(Component.literal("InfinityStorageManager is not initialized.")); + return 0; + } + + int given = 0; + for (UUID id : mgr.getAllLoadedUUIDs()) { + ItemStack stack = InfinityBigIntegerCellItem.withUUID(id); + if (!player.getInventory().add(stack)) { + // 若玩家物品栏已满,则扔在地上 + player.drop(stack, false); + } + given++; + } + final int finalGiven = given; + source.sendSuccess(() -> Component.literal("Gave " + finalGiven + " infinity disks."), false); + return given; + } catch (Exception ex) { + source.sendFailure(Component.literal("Error: " + ex.getMessage())); + return 0; + } + } +} + + diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java index 5f8f3fe..6ba1080 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -48,6 +48,13 @@ public class InfinityStorageManager extends SavedData { */ private final Map cells = new HashMap<>(); + /** + * 返回当前已加载的所有 UUID 的不可变视图,用于命令或调试用途 + */ + public java.util.Set getAllLoadedUUIDs() { + return java.util.Collections.unmodifiableSet(cells.keySet()); + } + public InfinityStorageManager() { setDirty(); } @@ -203,6 +210,19 @@ public class InfinityStorageManager extends SavedData { File file = level.getServer().getWorldPath(new LevelResource("data")) .resolve(FILE_NAME + ".dat").toFile(); NbtIo.writeCompressed(nbt, file); + + // 打印所有已保存的无限磁盘 UUID 及相关信息 + StringBuilder sb = new StringBuilder(); + sb.append("Saving Infinity Disks (UUIDs and info):\n"); + for (Map.Entry entry : cells.entrySet()) { + UUID uuid = entry.getKey(); + InfinityDataStorage data = entry.getValue(); + int types = (data.keys != null) ? data.keys.size() : 0; + sb.append(" - UUID: ").append(uuid) + .append(", Types: ").append(types) + .append("\n"); + } + LOGGER.info(sb.toString()); } } catch (Throwable ex) { LOGGER.info("InfinityStorageManager forceSaveAll error: {}", ex.getMessage()); From 45121b8647a5ec2f0a26bb2868928a578f1b36bc Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Mon, 22 Sep 2025 17:45:18 +0800 Subject: [PATCH 13/34] =?UTF-8?q?=E5=A2=9E=E5=8A=A0me=E9=A2=91=E9=81=93?= =?UTF-8?q?=E5=8D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ae/items/ChannelCardItem.java | 63 +++++++++++++++ .../bridge/InterfaceWirelessLinkBridge.java | 8 ++ .../com/extendedae_plus/init/ModItems.java | 7 ++ .../extendedae_plus/init/UpgradeCards.java | 12 ++- .../InterfaceLogicChannelCardMixin.java | 74 ++++++++++++++++++ .../InterfaceLogicChannelLinkBridge.java | 8 ++ .../helpers/InterfaceLogicTickerMixin.java | 30 +++++++ .../endpoint/InterfaceNodeEndpointImpl.java | 49 ++++++++++++ .../assets/extendedae_plus/lang/en_us.json | 7 +- .../assets/extendedae_plus/lang/zh_cn.json | 7 +- .../models/item/channel_card.json | 6 ++ .../textures/item/channel_card.png | Bin 0 -> 455 bytes .../resources/extendedae_plus.mixins.json | 4 +- 13 files changed, 271 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java create mode 100644 src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelLinkBridge.java create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java create mode 100644 src/main/java/com/extendedae_plus/wireless/endpoint/InterfaceNodeEndpointImpl.java create mode 100644 src/main/resources/assets/extendedae_plus/models/item/channel_card.json create mode 100644 src/main/resources/assets/extendedae_plus/textures/item/channel_card.png diff --git a/src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java b/src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java new file mode 100644 index 0000000..1d68551 --- /dev/null +++ b/src/main/java/com/extendedae_plus/ae/items/ChannelCardItem.java @@ -0,0 +1,63 @@ +package com.extendedae_plus.ae.items; + +import appeng.items.materials.UpgradeCardItem; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * 频道卡(MVP):仅存储一个 long 类型的频道号到 NBT:"channel"。 + * 继承 AE2 的 UpgradeCardItem 以复用升级卡判定与提示框架。 + */ +public class ChannelCardItem extends UpgradeCardItem { + public static final String TAG_CHANNEL = "channel"; + + public ChannelCardItem(Item.Properties properties) { + super(properties); + } + + public static void setChannel(ItemStack stack, long channel) { + CompoundTag tag = stack.getOrCreateTag(); + tag.putLong(TAG_CHANNEL, channel); + } + + public static long getChannel(ItemStack stack) { + CompoundTag tag = stack.getTag(); + return tag != null && tag.contains(TAG_CHANNEL) ? tag.getLong(TAG_CHANNEL) : 0L; + } + + @Override + public void appendHoverText(ItemStack stack, @Nullable Level level, List lines, TooltipFlag flag) { + super.appendHoverText(stack, level, lines, flag); + long ch = getChannel(stack); + if (ch == 0L) { + lines.add(Component.translatable("item.extendedae_plus.channel_card.channel.unset")); + } else { + lines.add(Component.translatable("item.extendedae_plus.channel_card.channel", ch)); + } + } + + @Override + public InteractionResultHolder use(Level level, Player player, InteractionHand hand) { + ItemStack stack = player.getItemInHand(hand); + if (!level.isClientSide) { + long ch = getChannel(stack); + boolean dec = player.isShiftKeyDown(); + long next = dec ? Math.max(0L, ch - 1L) : ch + 1L; + if (next != ch) { + setChannel(stack, next); + player.displayClientMessage(Component.translatable("item.extendedae_plus.channel_card.set", next), true); + } + } + return InteractionResultHolder.sidedSuccess(stack, level.isClientSide); + } +} diff --git a/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java b/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java new file mode 100644 index 0000000..097b091 --- /dev/null +++ b/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java @@ -0,0 +1,8 @@ +package com.extendedae_plus.bridge; + +/** + * 非 mixin 包下的桥接接口,供 mixin 进行 instanceof 检测和回调。 + */ +public interface InterfaceWirelessLinkBridge { + void extendedae_plus$updateWirelessLink(); +} diff --git a/src/main/java/com/extendedae_plus/init/ModItems.java b/src/main/java/com/extendedae_plus/init/ModItems.java index 0f7b430..79b4209 100644 --- a/src/main/java/com/extendedae_plus/init/ModItems.java +++ b/src/main/java/com/extendedae_plus/init/ModItems.java @@ -5,6 +5,7 @@ import appeng.api.parts.PartModels; import appeng.items.parts.PartModelsHelper; import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem; +import com.extendedae_plus.ae.items.ChannelCardItem; import com.extendedae_plus.ae.items.EntitySpeedTickerPartItem; import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem; import net.minecraft.world.item.BlockItem; @@ -76,6 +77,12 @@ public final class ModItems { "infinity_biginteger_cell", InfinityBigIntegerCellItem::new ); + // 频道卡(作为 AE 升级卡使用) + public static final RegistryObject CHANNEL_CARD = ITEMS.register( + "channel_card", + () -> new ChannelCardItem(new Item.Properties()) + ); + /** * 为 PartItem 注册 AE2 部件模型。 diff --git a/src/main/java/com/extendedae_plus/init/UpgradeCards.java b/src/main/java/com/extendedae_plus/init/UpgradeCards.java index ddd73d4..05dec0d 100644 --- a/src/main/java/com/extendedae_plus/init/UpgradeCards.java +++ b/src/main/java/com/extendedae_plus/init/UpgradeCards.java @@ -1,10 +1,15 @@ package com.extendedae_plus.init; import appeng.api.upgrades.Upgrades; +import appeng.core.definitions.AEBlocks; import appeng.core.definitions.AEItems; +import appeng.core.definitions.AEParts; +import appeng.core.localization.GuiText; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; - +/** + * + */ public class UpgradeCards { public UpgradeCards(final FMLCommonSetupEvent event) { event.enqueueWork(() -> { @@ -12,6 +17,11 @@ public class UpgradeCards { Upgrades.add(AEItems.ENERGY_CARD, ModItems.ENTITY_TICKER_PART_ITEM.get(), 8, "group.entity_ticker.name"); // 使用单一的 UpgradeCard Item 作为注册键,总共允许安装 4 张(不同等级由 ItemStack NBT 区分) Upgrades.add(ModItems.ENTITY_SPEED_CARD.get(), ModItems.ENTITY_TICKER_PART_ITEM.get(), 4, "group.entity_ticker.name"); + + // 新增:频道卡仅允许安装在 ME 接口(方块与部件)上,每台最多 1 张 + String interfaceGroup = GuiText.Interface.getTranslationKey(); + Upgrades.add(ModItems.CHANNEL_CARD.get(), AEBlocks.INTERFACE, 1, interfaceGroup); + Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.INTERFACE, 1, interfaceGroup); }); } } \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java new file mode 100644 index 0000000..659b120 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java @@ -0,0 +1,74 @@ +package com.extendedae_plus.mixin.ae2.helpers; + +import appeng.api.upgrades.IUpgradeInventory; +import appeng.helpers.InterfaceLogic; +import appeng.helpers.InterfaceLogicHost; +import com.extendedae_plus.ae.items.ChannelCardItem; +import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import com.extendedae_plus.init.ModItems; +import com.extendedae_plus.wireless.IWirelessEndpoint; +import com.extendedae_plus.wireless.WirelessSlaveLink; +import com.extendedae_plus.wireless.endpoint.InterfaceNodeEndpointImpl; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +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; + +@Mixin(InterfaceLogic.class) +public abstract class InterfaceLogicChannelCardMixin implements InterfaceWirelessLinkBridge { + + @Shadow(remap = false) public abstract IUpgradeInventory getUpgrades(); + + @Shadow(remap = false) public abstract appeng.api.networking.IGridNode getActionableNode(); + + @Shadow(remap = false) protected InterfaceLogicHost host; + + @Unique + private WirelessSlaveLink extendedae_plus$link; + + @Inject(method = "onUpgradesChanged", at = @At("TAIL"), remap = false) + private void extendedae_plus$onUpgradesChangedTail(CallbackInfo ci) { + handleChannelCardChange(); + } + + @Inject(method = "readFromNBT", at = @At("TAIL"), remap = false) + private void extendedae_plus$afterReadNBT(net.minecraft.nbt.CompoundTag tag, CallbackInfo ci) { + // 重载后根据卡状态恢复连接 + handleChannelCardChange(); + } + + @Inject(method = "clearContent", at = @At("HEAD"), remap = false) + private void extendedae_plus$onClearContent(CallbackInfo ci) { + if (extendedae_plus$link != null) { + extendedae_plus$link.onUnloadOrRemove(); + } + } + + @Unique + private void handleChannelCardChange() { + var inv = getUpgrades(); + long channel = 0L; + for (ItemStack stack : inv) { + if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { + channel = ChannelCardItem.getChannel(stack); + break; + } + } + if (extendedae_plus$link == null) { + IWirelessEndpoint endpoint = new InterfaceNodeEndpointImpl(host, this::getActionableNode); + extendedae_plus$link = new WirelessSlaveLink(endpoint); + } + extendedae_plus$link.setFrequency(channel); + } + + @Override + public void extendedae_plus$updateWirelessLink() { + if (extendedae_plus$link != null) { + extendedae_plus$link.updateStatus(); + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelLinkBridge.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelLinkBridge.java new file mode 100644 index 0000000..498d023 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelLinkBridge.java @@ -0,0 +1,8 @@ +package com.extendedae_plus.bridge; + +/** + * 旧名兼容:已迁移到非 mixin 包,避免 Mixin 处理器禁止直接引用。 + */ +public interface InterfaceLogicChannelLinkBridge { + void extendedae_plus$updateWirelessLink(); +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java new file mode 100644 index 0000000..77f9cbc --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java @@ -0,0 +1,30 @@ +package com.extendedae_plus.mixin.ae2.helpers; + +import appeng.helpers.InterfaceLogic; +import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import org.spongepowered.asm.mixin.Final; +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.CallbackInfoReturnable; + +/** + * 注入到 InterfaceLogic.Ticker 的每tick回调,驱动无线链接状态更新。 + */ +@Mixin(targets = "appeng.helpers.InterfaceLogic$Ticker") +public abstract class InterfaceLogicTickerMixin { + + // Mixin 访问内部类的外部引用字段(javac 生成名 this$0) + @Shadow(remap = false) + @Final + private InterfaceLogic this$0; + + @Inject(method = "tickingRequest", at = @At("TAIL"), remap = false) + private void extendedae_plus$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall, + CallbackInfoReturnable cir) { + if (this$0 instanceof InterfaceWirelessLinkBridge bridge) { + bridge.extendedae_plus$updateWirelessLink(); + } + } +} diff --git a/src/main/java/com/extendedae_plus/wireless/endpoint/InterfaceNodeEndpointImpl.java b/src/main/java/com/extendedae_plus/wireless/endpoint/InterfaceNodeEndpointImpl.java new file mode 100644 index 0000000..6736f4c --- /dev/null +++ b/src/main/java/com/extendedae_plus/wireless/endpoint/InterfaceNodeEndpointImpl.java @@ -0,0 +1,49 @@ +package com.extendedae_plus.wireless.endpoint; + +import appeng.api.networking.IGridNode; +import appeng.helpers.InterfaceLogicHost; +import com.extendedae_plus.wireless.IWirelessEndpoint; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; + +import java.util.Objects; +import java.util.function.Supplier; + +/** + * IWirelessEndpoint 实现:基于 InterfaceLogicHost 与节点提供者。 + */ +public class InterfaceNodeEndpointImpl implements IWirelessEndpoint { + private final InterfaceLogicHost host; + private final Supplier nodeSupplier; + + public InterfaceNodeEndpointImpl(InterfaceLogicHost host, Supplier nodeSupplier) { + this.host = Objects.requireNonNull(host); + this.nodeSupplier = Objects.requireNonNull(nodeSupplier); + } + + @Override + public ServerLevel getServerLevel() { + var be = host.getBlockEntity(); + if (be == null) return null; + Level lvl = be.getLevel(); + return (lvl instanceof ServerLevel) ? (ServerLevel) lvl : null; + } + + @Override + public BlockPos getBlockPos() { + var be = host.getBlockEntity(); + return be != null ? be.getBlockPos() : BlockPos.ZERO; + } + + @Override + public IGridNode getGridNode() { + return nodeSupplier.get(); + } + + @Override + public boolean isEndpointRemoved() { + var be = host.getBlockEntity(); + return be == null || be.isRemoved(); + } +} diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json index 320a792..ef955fa 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -87,5 +87,10 @@ "config.extendedae_plus.option.entityTickerBlackList": "Entity Ticker Blacklist", "config.extendedae_plus.option.entityTickerMultipliers": "Entity Ticker Extra Consumption Multipliers", "config.extendedae_plus.option.craftingPauseThreshold": "AE synthesis calculation pause check threshold", - "block.extendedae_plus.assembler_matrix_upload_core": "Assembler Matrix Upload Core" + "block.extendedae_plus.assembler_matrix_upload_core": "Assembler Matrix Upload Core", + + "item.extendedae_plus.channel_card": "Channel Card", + "item.extendedae_plus.channel_card.channel": "Frequency: %s", + "item.extendedae_plus.channel_card.channel.unset": "Frequency: Unset", + "item.extendedae_plus.channel_card.set": "Frequency set to: %s" } \ No newline at end of file diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index d7996ab..210532c 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -92,5 +92,10 @@ "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_success": "样板已上传到装配矩阵", "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_not_crafting": "仅支持上传合成样板,处理样板将被忽略", "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_no_matrix": "未在当前网络中找到已成型的装配矩阵", - "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_full": "装配矩阵的样板仓已满或无法插入" + "block.extendedae_plus.assembler_matrix_upload_core.tooltip.upload_fail_full": "装配矩阵的样板仓已满或无法插入", + + "item.extendedae_plus.channel_card": "频道卡", + "item.extendedae_plus.channel_card.channel": "频率:%s", + "item.extendedae_plus.channel_card.channel.unset": "频率:未设置", + "item.extendedae_plus.channel_card.set": "已设置频率:%s" } \ No newline at end of file diff --git a/src/main/resources/assets/extendedae_plus/models/item/channel_card.json b/src/main/resources/assets/extendedae_plus/models/item/channel_card.json new file mode 100644 index 0000000..8699938 --- /dev/null +++ b/src/main/resources/assets/extendedae_plus/models/item/channel_card.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "extendedae_plus:item/channel_card" + } +} diff --git a/src/main/resources/assets/extendedae_plus/textures/item/channel_card.png b/src/main/resources/assets/extendedae_plus/textures/item/channel_card.png new file mode 100644 index 0000000000000000000000000000000000000000..2fe67ab91aa171c096bdf1c628d56947b2663360 GIT binary patch literal 455 zcmV;&0XY7NP)oOTmiB@DmIl35%|JuY>F2;O!3E$3d;}_*)Yk`AHwmg5 xM&r^95{Cy$a#9jFGr=+qx+ZJ^h^~e#J^)VZn#5&}mr?)#002ovPDHLkV1gDyzjXir literal 0 HcmV?d00001 diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index aaad6df..a856a94 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -67,7 +67,9 @@ "extendedae.common.TileExPatternProviderMixin", "extendedae.container.ContainerExPatternProviderMixin", "extendedae.container.ContainerExPatternTerminalMixin", - "extendedae.container.ContainerWirelessExPatternTerminalMixin" + "extendedae.container.ContainerWirelessExPatternTerminalMixin", + "ae2.helpers.InterfaceLogicChannelCardMixin", + "ae2.helpers.InterfaceLogicTickerMixin" ], "injectors": { "defaultRequire": 1 From 8a4950f2446e2588cd55fcbca73e1b18027b57c0 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Mon, 22 Sep 2025 18:24:29 +0800 Subject: [PATCH 14/34] =?UTF-8?q?=E4=B8=BA=E6=A0=B7=E6=9D=BF=E4=BE=9B?= =?UTF-8?q?=E5=BA=94=E5=99=A8=E6=B7=BB=E5=8A=A0=E5=8D=87=E7=BA=A7=E6=A7=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bridge/IUpgradableMenu.java | 12 ++ .../PatternProviderScreenUpgradesMixin.java | 48 ++++++++ ...PatternProviderLogicHostUpgradesMixin.java | 21 ++++ .../PatternProviderLogicTickerMixin.java | 30 +++++ .../PatternProviderLogicUpgradesMixin.java | 112 ++++++++++++++++++ .../PatternProviderMenuUpgradesMixin.java | 49 ++++++++ .../endpoint/GenericNodeEndpointImpl.java | 49 ++++++++ .../resources/extendedae_plus.mixins.json | 5 + 8 files changed, 326 insertions(+) create mode 100644 src/main/java/com/extendedae_plus/bridge/IUpgradableMenu.java create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicHostUpgradesMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java create mode 100644 src/main/java/com/extendedae_plus/wireless/endpoint/GenericNodeEndpointImpl.java diff --git a/src/main/java/com/extendedae_plus/bridge/IUpgradableMenu.java b/src/main/java/com/extendedae_plus/bridge/IUpgradableMenu.java new file mode 100644 index 0000000..470ccd5 --- /dev/null +++ b/src/main/java/com/extendedae_plus/bridge/IUpgradableMenu.java @@ -0,0 +1,12 @@ +package com.extendedae_plus.bridge; + +import appeng.api.upgrades.IUpgradeInventory; +import appeng.menu.ToolboxMenu; + +public interface IUpgradableMenu { + ToolboxMenu getToolbox(); + IUpgradeInventory getUpgrades(); + default boolean hasUpgrade(net.minecraft.world.level.ItemLike upgradeCard) { + return getUpgrades().isInstalled(upgradeCard); + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java new file mode 100644 index 0000000..5e94289 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java @@ -0,0 +1,48 @@ +package com.extendedae_plus.mixin.ae2.client.gui; + +import appeng.api.upgrades.Upgrades; +import appeng.client.gui.AEBaseScreen; +import appeng.client.gui.implementations.PatternProviderScreen; +import appeng.client.gui.style.ScreenStyle; +import appeng.client.gui.widgets.ToolboxPanel; +import appeng.client.gui.widgets.UpgradesPanel; +import appeng.core.localization.GuiText; +import appeng.menu.SlotSemantics; +import appeng.menu.implementations.PatternProviderMenu; +import com.extendedae_plus.bridge.IUpgradableMenu; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; +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; + +import java.util.ArrayList; +import java.util.List; + +@Mixin(value = PatternProviderScreen.class, remap = false) +public abstract class PatternProviderScreenUpgradesMixin extends AEBaseScreen { + + @Inject(method = "", at = @At("TAIL")) + private void extendedae_plus$initUpgrades(PatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { + this.widgets.add("upgrades", new UpgradesPanel( + menu.getSlots(SlotSemantics.UPGRADE), + this::extendedae_plus$getCompatibleUpgrades)); + if (((IUpgradableMenu) menu).getToolbox().isPresent()) { + this.widgets.add("toolbox", new ToolboxPanel(style, ((IUpgradableMenu) menu).getToolbox().getName())); + } + } + + @Unique + private List extendedae_plus$getCompatibleUpgrades() { + var list = new ArrayList(); + list.add(GuiText.CompatibleUpgrades.text()); + list.addAll(Upgrades.getTooltipLinesForMachine(((IUpgradableMenu) menu).getUpgrades().getUpgradableItem())); + return list; + } + + public PatternProviderScreenUpgradesMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) { + super(menu, playerInventory, title, style); + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicHostUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicHostUpgradesMixin.java new file mode 100644 index 0000000..9254e5d --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicHostUpgradesMixin.java @@ -0,0 +1,21 @@ +package com.extendedae_plus.mixin.ae2.helpers.patternprovider; + +import appeng.api.upgrades.IUpgradeInventory; +import appeng.api.upgrades.IUpgradeableObject; +import appeng.helpers.patternprovider.PatternProviderLogic; +import appeng.helpers.patternprovider.PatternProviderLogicHost; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +/** + * 让 PatternProviderLogicHost 作为 IUpgradeableObject 的代理,菜单可从 host 获取升级槽。 + */ +@Mixin(value = PatternProviderLogicHost.class, remap = false) +public interface PatternProviderLogicHostUpgradesMixin extends IUpgradeableObject { + @Shadow PatternProviderLogic getLogic(); + + @Override + default IUpgradeInventory getUpgrades() { + return ((IUpgradeableObject) this.getLogic()).getUpgrades(); + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java new file mode 100644 index 0000000..8baebff --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java @@ -0,0 +1,30 @@ +package com.extendedae_plus.mixin.ae2.helpers.patternprovider; + +import appeng.helpers.patternprovider.PatternProviderLogic; +import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import org.spongepowered.asm.mixin.Final; +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.CallbackInfoReturnable; + +/** + * 注入到 PatternProviderLogic.Ticker 的每tick回调,驱动无线链接状态更新。 + */ +@Mixin(targets = "appeng.helpers.patternprovider.PatternProviderLogic$Ticker", remap = false) +public abstract class PatternProviderLogicTickerMixin { + + // Mixin 访问内部类的外部引用字段(javac 生成名 this$0) + @Shadow(remap = false) + @Final + private PatternProviderLogic this$0; + + @Inject(method = "tickingRequest", at = @At("TAIL")) + private void extendedae_plus$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall, + CallbackInfoReturnable cir) { + if (this$0 instanceof InterfaceWirelessLinkBridge bridge) { + bridge.extendedae_plus$updateWirelessLink(); + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java new file mode 100644 index 0000000..82e5bb0 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java @@ -0,0 +1,112 @@ +package com.extendedae_plus.mixin.ae2.helpers.patternprovider; + +import appeng.api.networking.IManagedGridNode; +import appeng.api.networking.security.IActionSource; +import appeng.api.upgrades.IUpgradeInventory; +import appeng.api.upgrades.IUpgradeableObject; +import appeng.api.upgrades.UpgradeInventories; +import appeng.helpers.patternprovider.PatternProviderLogic; +import appeng.helpers.patternprovider.PatternProviderLogicHost; +import com.extendedae_plus.ae.items.ChannelCardItem; +import com.extendedae_plus.init.ModItems; +import com.extendedae_plus.wireless.WirelessSlaveLink; +import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl; +import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +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; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.ItemStack; + +import java.util.List; + +/** + * 为 PatternProviderLogic 注入升级槽,实现 IUpgradeableObject。 + * 仅负责升级槽的持久化/掉落/清空与初始化,不改变原有逻辑。 + */ +@Mixin(value = PatternProviderLogic.class, remap = false) +public abstract class PatternProviderLogicUpgradesMixin implements IUpgradeableObject, InterfaceWirelessLinkBridge { + @Unique + private IUpgradeInventory extendedae_plus$upgrades = UpgradeInventories.empty(); + + @Unique + private WirelessSlaveLink extendedae_plus$link; + + @Final + @Shadow + private PatternProviderLogicHost host; + + @Final + @Shadow + private IManagedGridNode mainNode; + + @Final + @Shadow + private IActionSource actionSource; + + @Unique + private void extendedae_plus$onUpgradesChanged() { + this.host.saveChanges(); + // 读取频道卡,更新无线链接频率 + long channel = 0L; + for (var stack : this.extendedae_plus$upgrades) { + if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { + channel = ChannelCardItem.getChannel(stack); + break; + } + } + if (extendedae_plus$link == null) { + var endpoint = new GenericNodeEndpointImpl(() -> host.getBlockEntity(), () -> this.mainNode.getNode()); + extendedae_plus$link = new WirelessSlaveLink(endpoint); + } + extendedae_plus$link.setFrequency(channel); + extendedae_plus$link.updateStatus(); + } + + @Override + public IUpgradeInventory getUpgrades() { + return this.extendedae_plus$upgrades; + } + + @Inject(method = "(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/patternprovider/PatternProviderLogicHost;I)V", + at = @At("TAIL")) + private void extendedae_plus$initUpgrades(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) { + this.extendedae_plus$upgrades = UpgradeInventories.forMachine(host.getTerminalIcon().getItem(), 1, this::extendedae_plus$onUpgradesChanged); + } + + @Inject(method = "writeToNBT", at = @At("TAIL")) + private void extendedae_plus$saveUpgrades(CompoundTag tag, CallbackInfo ci) { + this.extendedae_plus$upgrades.writeToNBT(tag, "upgrades"); + } + + @Inject(method = "readFromNBT", at = @At("TAIL")) + private void extendedae_plus$loadUpgrades(CompoundTag tag, CallbackInfo ci) { + this.extendedae_plus$upgrades.readFromNBT(tag, "upgrades"); + } + + @Inject(method = "addDrops", at = @At("TAIL")) + private void extendedae_plus$dropUpgrades(List drops, CallbackInfo ci) { + for (var stack : this.extendedae_plus$upgrades) { + if (!stack.isEmpty()) { + drops.add(stack); + } + } + } + + @Inject(method = "clearContent", at = @At("TAIL")) + private void extendedae_plus$clearUpgrades(CallbackInfo ci) { + this.extendedae_plus$upgrades.clear(); + } + + @Override + public void extendedae_plus$updateWirelessLink() { + if (extendedae_plus$link != null) { + extendedae_plus$link.updateStatus(); + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java new file mode 100644 index 0000000..514b523 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java @@ -0,0 +1,49 @@ +package com.extendedae_plus.mixin.ae2.menu; + +import appeng.api.upgrades.IUpgradeInventory; +import appeng.api.upgrades.IUpgradeableObject; +import appeng.helpers.patternprovider.PatternProviderLogic; +import appeng.helpers.patternprovider.PatternProviderLogicHost; +import appeng.menu.AEBaseMenu; +import appeng.menu.ToolboxMenu; +import appeng.menu.implementations.PatternProviderMenu; +import com.extendedae_plus.bridge.IUpgradableMenu; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.MenuType; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +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; + +@Mixin(value = PatternProviderMenu.class, remap = false) +public abstract class PatternProviderMenuUpgradesMixin extends AEBaseMenu implements IUpgradableMenu { + @Final + @Shadow protected PatternProviderLogic logic; + + @Unique + private ToolboxMenu extendedae_plus$toolbox; + + @Inject(method = "(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", + at = @At("TAIL")) + private void extendedae_plus$initUpgrades(MenuType menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) { + this.extendedae_plus$toolbox = new ToolboxMenu(this); + this.setupUpgrades(((IUpgradeableObject) host).getUpgrades()); + } + + @Override + public ToolboxMenu getToolbox() { + return this.extendedae_plus$toolbox; + } + + @Override + public IUpgradeInventory getUpgrades() { + return ((IUpgradeableObject) this.logic).getUpgrades(); + } + + public PatternProviderMenuUpgradesMixin(MenuType menuType, int id, Inventory playerInventory, Object host) { + super(menuType, id, playerInventory, host); + } +} diff --git a/src/main/java/com/extendedae_plus/wireless/endpoint/GenericNodeEndpointImpl.java b/src/main/java/com/extendedae_plus/wireless/endpoint/GenericNodeEndpointImpl.java new file mode 100644 index 0000000..84cd191 --- /dev/null +++ b/src/main/java/com/extendedae_plus/wireless/endpoint/GenericNodeEndpointImpl.java @@ -0,0 +1,49 @@ +package com.extendedae_plus.wireless.endpoint; + +import appeng.api.networking.IGridNode; +import com.extendedae_plus.wireless.IWirelessEndpoint; +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; + +import java.util.Objects; +import java.util.function.Supplier; + +/** + * 通用 IWirelessEndpoint:通过提供方块实体与节点的 Supplier 实现。 + */ +public class GenericNodeEndpointImpl implements IWirelessEndpoint { + private final Supplier blockEntitySupplier; + private final Supplier nodeSupplier; + + public GenericNodeEndpointImpl(Supplier blockEntitySupplier, Supplier nodeSupplier) { + this.blockEntitySupplier = Objects.requireNonNull(blockEntitySupplier); + this.nodeSupplier = Objects.requireNonNull(nodeSupplier); + } + + @Override + public ServerLevel getServerLevel() { + var be = blockEntitySupplier.get(); + if (be == null) return null; + Level lvl = be.getLevel(); + return (lvl instanceof ServerLevel) ? (ServerLevel) lvl : null; + } + + @Override + public BlockPos getBlockPos() { + var be = blockEntitySupplier.get(); + return be != null ? be.getBlockPos() : BlockPos.ZERO; + } + + @Override + public IGridNode getGridNode() { + return nodeSupplier.get(); + } + + @Override + public boolean isEndpointRemoved() { + var be = blockEntitySupplier.get(); + return be == null || be.isRemoved(); + } +} diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index a856a94..b584236 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -20,6 +20,7 @@ "ae2.client.gui.PatternEncodingTermScreenMixin", "ae2.client.gui.PatternProviderCloseMixin", "ae2.client.gui.PatternProviderScreenMixin", + "ae2.client.gui.PatternProviderScreenUpgradesMixin", "ae2.client.gui.SlotGridLayoutMixin", "ae2.menu.CraftConfirmMenuGoBackMixin", "extendedae.accessor.GuiExPatternTerminalAccessor", @@ -62,6 +63,10 @@ "ae2.menu.PatternEncodingTermMenuMixin", "ae2.menu.PatternProviderMenuAdvancedMixin", "ae2.menu.PatternProviderMenuDoublingMixin", + "ae2.helpers.patternprovider.PatternProviderLogicUpgradesMixin", + "ae2.helpers.patternprovider.PatternProviderLogicHostUpgradesMixin", + "ae2.menu.PatternProviderMenuUpgradesMixin", + "ae2.helpers.patternprovider.PatternProviderLogicTickerMixin", "ae2WTlib.ContainerUWirelessExPatternTerminalMixin", "extendedae.common.PartExPatternProviderMixin", "extendedae.common.TileExPatternProviderMixin", From 1589f179cdc6d39f2d4f72ddf1810179a380ca4c Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Mon, 22 Sep 2025 19:01:13 +0800 Subject: [PATCH 15/34] =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=9B=B4=E5=A4=9A?= =?UTF-8?q?=E6=96=B9=E5=9D=97=E6=8F=92=E5=85=A5=E9=A2=91=E9=81=93=E5=8D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../extendedae_plus/init/UpgradeCards.java | 33 ++++++++++ .../automation/IOBusPartChannelCardMixin.java | 65 +++++++++++++++++++ .../IOBusPartTickerChannelCardMixin.java | 24 +++++++ .../StorageBusPartChannelCardMixin.java | 63 ++++++++++++++++++ .../StorageBusPartTickerChannelCardMixin.java | 24 +++++++ .../resources/extendedae_plus.mixins.json | 4 ++ 6 files changed, 213 insertions(+) create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartTickerChannelCardMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartTickerChannelCardMixin.java diff --git a/src/main/java/com/extendedae_plus/init/UpgradeCards.java b/src/main/java/com/extendedae_plus/init/UpgradeCards.java index 05dec0d..d3a0089 100644 --- a/src/main/java/com/extendedae_plus/init/UpgradeCards.java +++ b/src/main/java/com/extendedae_plus/init/UpgradeCards.java @@ -6,6 +6,14 @@ import appeng.core.definitions.AEItems; import appeng.core.definitions.AEParts; import appeng.core.localization.GuiText; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_PATTERN_PROVIDER; +import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_PATTERN_PROVIDER_PART; +import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_INTERFACE; +import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_INTERFACE_PART; +import static com.glodblock.github.extendedae.common.EPPItemAndBlock.OVERSIZE_INTERFACE; +import static com.glodblock.github.extendedae.common.EPPItemAndBlock.OVERSIZE_INTERFACE_PART; +import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_IMPORT_BUS; +import static com.glodblock.github.extendedae.common.EPPItemAndBlock.EX_EXPORT_BUS; /** * @@ -22,6 +30,31 @@ public class UpgradeCards { String interfaceGroup = GuiText.Interface.getTranslationKey(); Upgrades.add(ModItems.CHANNEL_CARD.get(), AEBlocks.INTERFACE, 1, interfaceGroup); Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.INTERFACE, 1, interfaceGroup); + + // 新增:样板供应器(方块与部件)支持频道卡,每台最多 1 张 + String patternProviderGroup = "group.pattern_provider.name"; + Upgrades.add(ModItems.CHANNEL_CARD.get(), AEBlocks.PATTERN_PROVIDER, 1, patternProviderGroup); + Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.PATTERN_PROVIDER, 1, patternProviderGroup); + + // ExtendedAE 的扩展样板供应器(方块与部件) + Upgrades.add(ModItems.CHANNEL_CARD.get(),EX_PATTERN_PROVIDER, 1, patternProviderGroup); + Upgrades.add(ModItems.CHANNEL_CARD.get(),EX_PATTERN_PROVIDER_PART, 1, patternProviderGroup); + + //EAE 的扩展接口与超大接口(方块与部件)支持频道卡 + Upgrades.add(ModItems.CHANNEL_CARD.get(), EX_INTERFACE, 1, interfaceGroup); + Upgrades.add(ModItems.CHANNEL_CARD.get(), EX_INTERFACE_PART, 1, interfaceGroup); + Upgrades.add(ModItems.CHANNEL_CARD.get(), OVERSIZE_INTERFACE, 1, interfaceGroup); + Upgrades.add(ModItems.CHANNEL_CARD.get(), OVERSIZE_INTERFACE_PART, 1, interfaceGroup); + + //AE2 的输入/输出/存储总线支持频道卡(部件) + String ioBusGroup = GuiText.IOBuses.getTranslationKey(); + Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.IMPORT_BUS, 1, ioBusGroup); + Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.EXPORT_BUS, 1, ioBusGroup); + Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.STORAGE_BUS, 1, ioBusGroup); + + //EAE 的扩展输入/输出总线支持频道卡(部件) + Upgrades.add(ModItems.CHANNEL_CARD.get(), EX_IMPORT_BUS, 1, ioBusGroup); + Upgrades.add(ModItems.CHANNEL_CARD.get(), EX_EXPORT_BUS, 1, ioBusGroup); }); } } \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java new file mode 100644 index 0000000..c556c8f --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java @@ -0,0 +1,65 @@ +package com.extendedae_plus.mixin.ae2.parts.automation; + +import appeng.api.networking.security.IActionHost; +import appeng.api.upgrades.IUpgradeInventory; +import appeng.api.upgrades.IUpgradeableObject; +import appeng.helpers.InterfaceLogicHost; +import appeng.parts.automation.IOBusPart; +import com.extendedae_plus.ae.items.ChannelCardItem; +import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import com.extendedae_plus.init.ModItems; +import com.extendedae_plus.wireless.WirelessSlaveLink; +import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl; +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; + +/** + * 给 AE2 的 I/O 总线注入频道卡联动:在升级变更时读取频道并更新无线链接。 + */ +@Mixin(value = IOBusPart.class, remap = false) +public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLinkBridge, IUpgradeableObject { + + @Unique + private WirelessSlaveLink extendedae_plus$link; + + @Inject(method = "upgradesChanged", at = @At("TAIL")) + private void extendedae_plus$onUpgradesChanged(CallbackInfo ci) { + IUpgradeInventory inv = this.getUpgrades(); + long channel = 0L; + boolean found = false; + for (var stack : inv) { + if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { + channel = ChannelCardItem.getChannel(stack); + found = true; + break; + } + } + if (!found) { + // 无频道卡则断开 + if (extendedae_plus$link != null) { + extendedae_plus$link.setFrequency(0L); + extendedae_plus$link.updateStatus(); + } + return; + } + if (extendedae_plus$link == null) { + var endpoint = new GenericNodeEndpointImpl( + () -> ((appeng.parts.AEBasePart)(Object)this).getHost().getBlockEntity(), + () -> ((IActionHost)(Object)this).getActionableNode() + ); + extendedae_plus$link = new WirelessSlaveLink(endpoint); + } + extendedae_plus$link.setFrequency(channel); + extendedae_plus$link.updateStatus(); + } + + @Override + public void extendedae_plus$updateWirelessLink() { + if (extendedae_plus$link != null) { + extendedae_plus$link.updateStatus(); + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartTickerChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartTickerChannelCardMixin.java new file mode 100644 index 0000000..c5bd28f --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartTickerChannelCardMixin.java @@ -0,0 +1,24 @@ +package com.extendedae_plus.mixin.ae2.parts.automation; + +import appeng.api.networking.IGridNode; +import appeng.api.networking.ticking.TickRateModulation; +import appeng.parts.automation.IOBusPart; +import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +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; + +/** + * 在 I/O 总线的 tickingRequest 尾部驱动无线链接刷新。 + */ +@Mixin(value = IOBusPart.class, remap = false) +public abstract class IOBusPartTickerChannelCardMixin { + + @Inject(method = "tickingRequest", at = @At("TAIL")) + private void extendedae_plus$tickTail(IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable cir) { + if (this instanceof InterfaceWirelessLinkBridge bridge) { + bridge.extendedae_plus$updateWirelessLink(); + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java new file mode 100644 index 0000000..2abb221 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java @@ -0,0 +1,63 @@ +package com.extendedae_plus.mixin.ae2.parts.storagebus; + +import appeng.api.networking.security.IActionHost; +import appeng.api.upgrades.IUpgradeInventory; +import appeng.api.upgrades.IUpgradeableObject; +import appeng.parts.storagebus.StorageBusPart; +import com.extendedae_plus.ae.items.ChannelCardItem; +import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import com.extendedae_plus.init.ModItems; +import com.extendedae_plus.wireless.WirelessSlaveLink; +import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl; +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; + +/** + * 给 AE2 的存储总线注入频道卡联动:在升级变更时读取频道并更新无线链接。 + */ +@Mixin(value = StorageBusPart.class, remap = false) +public abstract class StorageBusPartChannelCardMixin implements InterfaceWirelessLinkBridge, IUpgradeableObject { + + @Unique + private WirelessSlaveLink extendedae_plus$link; + + @Inject(method = "upgradesChanged", at = @At("TAIL")) + private void extendedae_plus$onUpgradesChanged(CallbackInfo ci) { + IUpgradeInventory inv = this.getUpgrades(); + long channel = 0L; + boolean found = false; + for (var stack : inv) { + if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { + channel = ChannelCardItem.getChannel(stack); + found = true; + break; + } + } + if (!found) { + if (extendedae_plus$link != null) { + extendedae_plus$link.setFrequency(0L); + extendedae_plus$link.updateStatus(); + } + return; + } + if (extendedae_plus$link == null) { + var endpoint = new GenericNodeEndpointImpl( + () -> ((appeng.parts.AEBasePart)(Object)this).getHost().getBlockEntity(), + () -> ((IActionHost)(Object)this).getActionableNode() + ); + extendedae_plus$link = new WirelessSlaveLink(endpoint); + } + extendedae_plus$link.setFrequency(channel); + extendedae_plus$link.updateStatus(); + } + + @Override + public void extendedae_plus$updateWirelessLink() { + if (extendedae_plus$link != null) { + extendedae_plus$link.updateStatus(); + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartTickerChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartTickerChannelCardMixin.java new file mode 100644 index 0000000..636b4e4 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartTickerChannelCardMixin.java @@ -0,0 +1,24 @@ +package com.extendedae_plus.mixin.ae2.parts.storagebus; + +import appeng.api.networking.IGridNode; +import appeng.api.networking.ticking.TickRateModulation; +import appeng.parts.storagebus.StorageBusPart; +import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +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; + +/** + * 在存储总线的 tickingRequest 尾部驱动无线链接刷新。 + */ +@Mixin(value = StorageBusPart.class, remap = false) +public abstract class StorageBusPartTickerChannelCardMixin { + + @Inject(method = "tickingRequest", at = @At("TAIL")) + private void extendedae_plus$tickTail(IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable cir) { + if (this instanceof InterfaceWirelessLinkBridge bridge) { + bridge.extendedae_plus$updateWirelessLink(); + } + } +} diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index b584236..48e0c2e 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -67,6 +67,10 @@ "ae2.helpers.patternprovider.PatternProviderLogicHostUpgradesMixin", "ae2.menu.PatternProviderMenuUpgradesMixin", "ae2.helpers.patternprovider.PatternProviderLogicTickerMixin", + "ae2.parts.automation.IOBusPartChannelCardMixin", + "ae2.parts.automation.IOBusPartTickerChannelCardMixin", + "ae2.parts.storagebus.StorageBusPartChannelCardMixin", + "ae2.parts.storagebus.StorageBusPartTickerChannelCardMixin", "ae2WTlib.ContainerUWirelessExPatternTerminalMixin", "extendedae.common.PartExPatternProviderMixin", "extendedae.common.TileExPatternProviderMixin", From 4745be106344e49fcd4f32053cd2fbf1a08e737c Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Mon, 22 Sep 2025 19:21:55 +0800 Subject: [PATCH 16/34] =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=9B=B4=E5=A4=9A?= =?UTF-8?q?=E6=96=B9=E5=9D=97=E6=8F=92=E5=85=A5=E9=A2=91=E9=81=93=E5=8D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/extendedae_plus/init/UpgradeCards.java | 3 ++- src/main/resources/assets/extendedae_plus/lang/en_us.json | 3 ++- src/main/resources/assets/extendedae_plus/lang/zh_cn.json | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/extendedae_plus/init/UpgradeCards.java b/src/main/java/com/extendedae_plus/init/UpgradeCards.java index d3a0089..c7b9c33 100644 --- a/src/main/java/com/extendedae_plus/init/UpgradeCards.java +++ b/src/main/java/com/extendedae_plus/init/UpgradeCards.java @@ -48,9 +48,10 @@ public class UpgradeCards { //AE2 的输入/输出/存储总线支持频道卡(部件) String ioBusGroup = GuiText.IOBuses.getTranslationKey(); + String storageGroup = "group.storage.name"; Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.IMPORT_BUS, 1, ioBusGroup); Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.EXPORT_BUS, 1, ioBusGroup); - Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.STORAGE_BUS, 1, ioBusGroup); + Upgrades.add(ModItems.CHANNEL_CARD.get(), AEParts.STORAGE_BUS, 1, storageGroup); //EAE 的扩展输入/输出总线支持频道卡(部件) Upgrades.add(ModItems.CHANNEL_CARD.get(), EX_IMPORT_BUS, 1, ioBusGroup); diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json index ef955fa..07156e3 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -92,5 +92,6 @@ "item.extendedae_plus.channel_card": "Channel Card", "item.extendedae_plus.channel_card.channel": "Frequency: %s", "item.extendedae_plus.channel_card.channel.unset": "Frequency: Unset", - "item.extendedae_plus.channel_card.set": "Frequency set to: %s" + "item.extendedae_plus.channel_card.set": "Frequency set to: %s", + "group.storage.name": "StorageBus" } \ No newline at end of file diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index 210532c..a245edb 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -97,5 +97,8 @@ "item.extendedae_plus.channel_card": "频道卡", "item.extendedae_plus.channel_card.channel": "频率:%s", "item.extendedae_plus.channel_card.channel.unset": "频率:未设置", - "item.extendedae_plus.channel_card.set": "已设置频率:%s" + "item.extendedae_plus.channel_card.set": "已设置频率:%s", + "group.pattern_provider.name": "样板供应器", + "group.entity_ticker.name": "实体加速器", + "group.storage.name": "存储总线" } \ No newline at end of file From fd075c76f47c4ca38baf3084e95a4cc222fc85f7 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Tue, 23 Sep 2025 10:40:02 +0800 Subject: [PATCH 17/34] =?UTF-8?q?=E4=BF=AE=E5=A4=8Diobus=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bridge/InterfaceWirelessLinkBridge.java | 14 ++ .../mixin/ae2/CraftingCPUClusterMixin.java | 62 ++++---- .../ae2/parts/AEBasePartClientSyncMixin.java | 68 +++++++++ .../automation/IOBusPartChannelCardMixin.java | 132 +++++++++++++++--- .../StorageBusPartChannelCardMixin.java | 128 ++++++++++++++--- .../resources/extendedae_plus.mixins.json | 1 + 6 files changed, 335 insertions(+), 70 deletions(-) create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/parts/AEBasePartClientSyncMixin.java diff --git a/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java b/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java index 097b091..11b9013 100644 --- a/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java +++ b/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java @@ -5,4 +5,18 @@ package com.extendedae_plus.bridge; */ public interface InterfaceWirelessLinkBridge { void extendedae_plus$updateWirelessLink(); + + /** + * 获取无线连接状态(服务端返回真实状态,客户端返回同步状态) + */ + default boolean extendedae_plus$isWirelessConnected() { + return false; + } + + /** + * 设置客户端的无线连接状态(仅在客户端使用) + */ + default void extendedae_plus$setClientWirelessState(boolean connected) { + // 默认实现为空 + } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/CraftingCPUClusterMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/CraftingCPUClusterMixin.java index 8faf483..7c76d90 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/CraftingCPUClusterMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/CraftingCPUClusterMixin.java @@ -1,36 +1,48 @@ package com.extendedae_plus.mixin.ae2; import appeng.blockentity.crafting.CraftingBlockEntity; +import appeng.blockentity.crafting.CraftingMonitorBlockEntity; import appeng.me.cluster.implementations.CraftingCPUCluster; +import appeng.me.helpers.MachineSource; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Constant; -import org.spongepowered.asm.mixin.injection.ModifyConstant; -import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.List; @Mixin(value = CraftingCPUCluster.class, remap = false, priority = 2000) public abstract class CraftingCPUClusterMixin { - // 1) 提升“单方块线程上限”的常量,避免抛出 IAE 的 IllegalArgumentException - @ModifyConstant( - method = "addBlockEntity(Lappeng/blockentity/crafting/CraftingBlockEntity;)V", - constant = @Constant(intValue = 16) - ) - private int extendedae_plus$raisePerUnitLimit(int original) { - // 放宽到极大值,完全取消单方块 16 线程的硬限制 - return Integer.MAX_VALUE; - } + + @Shadow private List blockEntities; + @Shadow private List status; + @Shadow private MachineSource machineSrc; + @Shadow private long storage; + @Shadow private int accelerator; + + /** + * 完全重写addBlockEntity方法,移除16线程的硬限制 + * @author ExtendedAE_Plus + * @reason 移除单方块16线程的硬限制,允许更高的线程数 + */ + @Overwrite + void addBlockEntity(CraftingBlockEntity te) { + if (this.machineSrc == null || te.isCoreBlock()) { + this.machineSrc = new MachineSource(te); + } - // 2) 保持统计使用原始线程值(若存在多处调用),不再返回固定 16 - @Redirect( - method = "addBlockEntity(Lappeng/blockentity/crafting/CraftingBlockEntity;)V", - at = @At( - value = "INVOKE", - target = "Lappeng/blockentity/crafting/CraftingBlockEntity;getAcceleratorThreads()I", - ordinal = 1 - ) - ) - private int extendedae_plus$onGetThreadsForLimitCheck(CraftingBlockEntity te) { - // 返回原始线程数,确保总并行单元不被错误下限 - return te.getAcceleratorThreads(); + te.setCoreBlock(false); + te.saveChanges(); + this.blockEntities.add(0, te); + + if (te instanceof CraftingMonitorBlockEntity) { + this.status.add((CraftingMonitorBlockEntity) te); + } + if (te.getStorageBytes() > 0) { + this.storage += te.getStorageBytes(); + } + if (te.getAcceleratorThreads() > 0) { + // 移除原来的16线程限制,直接添加线程数 + this.accelerator += te.getAcceleratorThreads(); + } } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/AEBasePartClientSyncMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/AEBasePartClientSyncMixin.java new file mode 100644 index 0000000..2ae0175 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/AEBasePartClientSyncMixin.java @@ -0,0 +1,68 @@ +package com.extendedae_plus.mixin.ae2.parts; + +import appeng.parts.AEBasePart; +import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import net.minecraft.network.FriendlyByteBuf; +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.CallbackInfoReturnable; + +/** + * 为所有AE2 Part添加无线连接状态的客户端同步功能 + */ +@Mixin(value = AEBasePart.class, remap = false) +public class AEBasePartClientSyncMixin { + + @Inject(method = "writeToStream", at = @At("TAIL")) + private void extendedae_plus$writeWirelessState(FriendlyByteBuf data, CallbackInfo ci) { + // 检查是否实现了无线链接桥接接口 + if (this instanceof InterfaceWirelessLinkBridge) { + InterfaceWirelessLinkBridge bridge = (InterfaceWirelessLinkBridge) this; + // 同步无线连接状态到客户端 + boolean connected = false; + try { + // 只在服务端获取真实连接状态 + AEBasePart part = (AEBasePart)(Object)this; + if (!part.isClientSide()) { + connected = bridge.extendedae_plus$isWirelessConnected(); + // 调试日志:记录写入的状态 + if (part.getClass().getSimpleName().contains("IOBus")) { + System.out.println("[写入状态] IOBus 无线连接状态: " + connected); + } + } + } catch (Exception e) { + // 忽略异常,默认为false + System.err.println("获取无线连接状态失败: " + e.getMessage()); + } + data.writeBoolean(connected); + } else { + // 不是无线链接Part,写入false + data.writeBoolean(false); + } + } + + @Inject(method = "readFromStream", at = @At("TAIL")) + private void extendedae_plus$readWirelessState(FriendlyByteBuf data, CallbackInfoReturnable cir) { + // 读取无线连接状态 + boolean connected = data.readBoolean(); + + // 检查是否实现了无线链接桥接接口 + if (this instanceof InterfaceWirelessLinkBridge) { + InterfaceWirelessLinkBridge bridge = (InterfaceWirelessLinkBridge) this; + try { + // 调试日志:记录读取的状态 + AEBasePart part = (AEBasePart)(Object)this; + if (part.getClass().getSimpleName().contains("IOBus")) { + System.out.println("[读取状态] IOBus 无线连接状态: " + connected); + } + // 更新客户端状态 + bridge.extendedae_plus$setClientWirelessState(connected); + } catch (Exception e) { + // 忽略异常 + System.err.println("设置客户端无线连接状态失败: " + e.getMessage()); + } + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java index c556c8f..cf21f1d 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java @@ -5,11 +5,14 @@ import appeng.api.upgrades.IUpgradeInventory; import appeng.api.upgrades.IUpgradeableObject; import appeng.helpers.InterfaceLogicHost; import appeng.parts.automation.IOBusPart; +import net.minecraft.nbt.CompoundTag; +import com.extendedae_plus.util.ExtendedAELogger; import com.extendedae_plus.ae.items.ChannelCardItem; import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; import com.extendedae_plus.init.ModItems; import com.extendedae_plus.wireless.WirelessSlaveLink; import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl; +import net.minecraft.network.FriendlyByteBuf; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -24,36 +27,105 @@ public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLink @Unique private WirelessSlaveLink extendedae_plus$link; + + @Unique + private long extendedae_plus$lastChannel = -1; + + @Unique + private boolean extendedae_plus$clientConnected = false; + + @Unique + private boolean extendedae_plus$hasTickInitialized = false; @Inject(method = "upgradesChanged", at = @At("TAIL")) private void extendedae_plus$onUpgradesChanged(CallbackInfo ci) { - IUpgradeInventory inv = this.getUpgrades(); - long channel = 0L; - boolean found = false; - for (var stack : inv) { - if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { - channel = ChannelCardItem.getChannel(stack); - found = true; - break; - } + // 只在服务端初始化频道链接 + if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) { + extendedae_plus$initializeChannelLink(); } - if (!found) { - // 无频道卡则断开 - if (extendedae_plus$link != null) { - extendedae_plus$link.setFrequency(0L); - extendedae_plus$link.updateStatus(); - } + } + + @Inject(method = "tickingRequest", at = @At("HEAD")) + private void extendedae_plus$beforeTick(appeng.api.networking.IGridNode node, int ticksSinceLastCall, org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable cir) { + // 在第一次tick时初始化频道链接(此时网格节点已经在线) + if (!extendedae_plus$hasTickInitialized && !((appeng.parts.AEBasePart)(Object)this).isClientSide()) { + extendedae_plus$hasTickInitialized = true; + extendedae_plus$initializeChannelLink(); + } + } + + @Inject(method = "readFromNBT", at = @At("TAIL")) + private void extendedae_plus$afterReadFromNBT(CompoundTag extra, CallbackInfo ci) { + // 从NBT加载时重置频道缓存和tick初始化标志 + if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) { + extendedae_plus$lastChannel = -1; + extendedae_plus$hasTickInitialized = false; // 重置标志,允许再次初始化 + } + } + + @Unique + private void extendedae_plus$initializeChannelLink() { + // 防止重复调用 + if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) { return; } - if (extendedae_plus$link == null) { - var endpoint = new GenericNodeEndpointImpl( - () -> ((appeng.parts.AEBasePart)(Object)this).getHost().getBlockEntity(), - () -> ((IActionHost)(Object)this).getActionableNode() - ); - extendedae_plus$link = new WirelessSlaveLink(endpoint); + + try { + IUpgradeInventory inv = this.getUpgrades(); + long channel = 0L; + boolean found = false; + for (var stack : inv) { + if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { + channel = ChannelCardItem.getChannel(stack); + found = true; + break; + } + } + + // 频道没有变化则跳过 + if (extendedae_plus$lastChannel == channel) { + return; + } + extendedae_plus$lastChannel = channel; + + ExtendedAELogger.LOGGER.debug("[服务端] IOBus 初始化频道链接: found={}, channel={}", found, channel); + + if (!found) { + // 无频道卡则断开 + if (extendedae_plus$link != null) { + extendedae_plus$link.setFrequency(0L); + extendedae_plus$link.updateStatus(); + ExtendedAELogger.LOGGER.debug("[服务端] IOBus 断开频道链接"); + // 立即通知客户端状态变化(断开连接无需延迟) + ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate(); + } + return; + } + + if (extendedae_plus$link == null) { + var endpoint = new GenericNodeEndpointImpl( + () -> ((appeng.parts.AEBasePart)(Object)this).getHost().getBlockEntity(), + () -> ((IActionHost)(Object)this).getActionableNode() + ); + extendedae_plus$link = new WirelessSlaveLink(endpoint); + ExtendedAELogger.LOGGER.debug("[服务端] IOBus 创建新的无线链接"); + } + + extendedae_plus$link.setFrequency(channel); + extendedae_plus$link.updateStatus(); + + // 调试信息:检查网格节点状态 + var gridNode = ((IActionHost)(Object)this).getActionableNode(); + ExtendedAELogger.LOGGER.debug("[服务端] IOBus 设置频道: {}, 连接状态: {}, 网格节点: {}, 在线: {}", + channel, extendedae_plus$link.isConnected(), + gridNode != null ? "exists" : "null", + gridNode != null ? gridNode.isOnline() : "N/A"); + + // 通知客户端状态变化 + ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate(); + } catch (Exception e) { + ExtendedAELogger.LOGGER.error("[服务端] IOBus 初始化频道链接失败", e); } - extendedae_plus$link.setFrequency(channel); - extendedae_plus$link.updateStatus(); } @Override @@ -62,4 +134,18 @@ public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLink extendedae_plus$link.updateStatus(); } } + + @Override + public boolean extendedae_plus$isWirelessConnected() { + if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) { + return extendedae_plus$clientConnected; + } else { + return extendedae_plus$link != null && extendedae_plus$link.isConnected(); + } + } + + @Override + public void extendedae_plus$setClientWirelessState(boolean connected) { + extendedae_plus$clientConnected = connected; + } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java index 2abb221..e4cc287 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java @@ -1,9 +1,13 @@ package com.extendedae_plus.mixin.ae2.parts.storagebus; +import appeng.api.networking.IGridNodeListener; import appeng.api.networking.security.IActionHost; import appeng.api.upgrades.IUpgradeInventory; import appeng.api.upgrades.IUpgradeableObject; import appeng.parts.storagebus.StorageBusPart; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import com.extendedae_plus.util.ExtendedAELogger; import com.extendedae_plus.ae.items.ChannelCardItem; import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; import com.extendedae_plus.init.ModItems; @@ -23,35 +27,101 @@ public abstract class StorageBusPartChannelCardMixin implements InterfaceWireles @Unique private WirelessSlaveLink extendedae_plus$link; + + @Unique + private long extendedae_plus$lastChannel = -1; + + @Unique + private boolean extendedae_plus$clientConnected = false; @Inject(method = "upgradesChanged", at = @At("TAIL")) private void extendedae_plus$onUpgradesChanged(CallbackInfo ci) { - IUpgradeInventory inv = this.getUpgrades(); - long channel = 0L; - boolean found = false; - for (var stack : inv) { - if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { - channel = ChannelCardItem.getChannel(stack); - found = true; - break; - } + // 只在服务端初始化频道链接 + if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) { + extendedae_plus$initializeChannelLink(); } - if (!found) { - if (extendedae_plus$link != null) { - extendedae_plus$link.setFrequency(0L); - extendedae_plus$link.updateStatus(); - } + } + + @Inject(method = "onMainNodeStateChanged", at = @At("TAIL")) + private void extendedae_plus$onMainNodeStateChanged(IGridNodeListener.State reason, CallbackInfo ci) { + // 在节点状态变化时(包括加载后的GRID_BOOT)重新初始化频道链接 + if (reason == IGridNodeListener.State.GRID_BOOT && !((appeng.parts.AEBasePart)(Object)this).isClientSide()) { + extendedae_plus$initializeChannelLink(); + } + } + + @Inject(method = "readFromNBT", at = @At("TAIL")) + private void extendedae_plus$afterReadFromNBT(CompoundTag extra, CallbackInfo ci) { + // 从NBT加载后也重新初始化频道链接(只在服务端) + if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) { + // 从NBT加载时重置频道缓存,强制重新初始化 + extendedae_plus$lastChannel = -1; + extendedae_plus$initializeChannelLink(); + } + } + + @Unique + private void extendedae_plus$initializeChannelLink() { + // 防止重复调用 + if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) { return; } - if (extendedae_plus$link == null) { - var endpoint = new GenericNodeEndpointImpl( - () -> ((appeng.parts.AEBasePart)(Object)this).getHost().getBlockEntity(), - () -> ((IActionHost)(Object)this).getActionableNode() - ); - extendedae_plus$link = new WirelessSlaveLink(endpoint); + + try { + IUpgradeInventory inv = this.getUpgrades(); + long channel = 0L; + boolean found = false; + for (var stack : inv) { + if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { + channel = ChannelCardItem.getChannel(stack); + found = true; + break; + } + } + + // 频道没有变化则跳过 + if (extendedae_plus$lastChannel == channel) { + return; + } + extendedae_plus$lastChannel = channel; + + ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 初始化频道链接: found={}, channel={}", found, channel); + + if (!found) { + if (extendedae_plus$link != null) { + extendedae_plus$link.setFrequency(0L); + extendedae_plus$link.updateStatus(); + ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 断开频道链接"); + // 通知客户端状态变化 + ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate(); + } + return; + } + + if (extendedae_plus$link == null) { + var endpoint = new GenericNodeEndpointImpl( + () -> ((appeng.parts.AEBasePart)(Object)this).getHost().getBlockEntity(), + () -> ((IActionHost)(Object)this).getActionableNode() + ); + extendedae_plus$link = new WirelessSlaveLink(endpoint); + ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 创建新的无线链接"); + } + + extendedae_plus$link.setFrequency(channel); + extendedae_plus$link.updateStatus(); + + // 调试信息:检查网格节点状态 + var gridNode = ((IActionHost)(Object)this).getActionableNode(); + ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 设置频道: {}, 连接状态: {}, 网格节点: {}, 在线: {}", + channel, extendedae_plus$link.isConnected(), + gridNode != null ? "exists" : "null", + gridNode != null ? gridNode.isOnline() : "N/A"); + + // 通知客户端状态变化 + ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate(); + } catch (Exception e) { + ExtendedAELogger.LOGGER.error("[服务端] StorageBus 初始化频道链接失败", e); } - extendedae_plus$link.setFrequency(channel); - extendedae_plus$link.updateStatus(); } @Override @@ -60,4 +130,18 @@ public abstract class StorageBusPartChannelCardMixin implements InterfaceWireles extendedae_plus$link.updateStatus(); } } + + @Override + public boolean extendedae_plus$isWirelessConnected() { + if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) { + return extendedae_plus$clientConnected; + } else { + return extendedae_plus$link != null && extendedae_plus$link.isConnected(); + } + } + + @Override + public void extendedae_plus$setClientWirelessState(boolean connected) { + extendedae_plus$clientConnected = connected; + } } diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index 48e0c2e..ab3d360 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -67,6 +67,7 @@ "ae2.helpers.patternprovider.PatternProviderLogicHostUpgradesMixin", "ae2.menu.PatternProviderMenuUpgradesMixin", "ae2.helpers.patternprovider.PatternProviderLogicTickerMixin", + "ae2.parts.AEBasePartClientSyncMixin", "ae2.parts.automation.IOBusPartChannelCardMixin", "ae2.parts.automation.IOBusPartTickerChannelCardMixin", "ae2.parts.storagebus.StorageBusPartChannelCardMixin", From 7f704e2a9c16cf767ad6829f2419a16396ecfe51 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Tue, 23 Sep 2025 10:48:44 +0800 Subject: [PATCH 18/34] =?UTF-8?q?=E4=BF=AE=E5=A4=8Diobus=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bridge/InterfaceWirelessLinkBridge.java | 6 +- .../PatternProviderScreenUpgradesMixin.java | 6 +- .../InterfaceLogicChannelCardMixin.java | 24 +++---- .../InterfaceLogicChannelLinkBridge.java | 2 +- .../helpers/InterfaceLogicTickerMixin.java | 4 +- .../PatternProviderLogicTickerMixin.java | 4 +- .../PatternProviderLogicUpgradesMixin.java | 44 ++++++------ .../PatternProviderMenuUpgradesMixin.java | 8 +-- .../ae2/parts/AEBasePartClientSyncMixin.java | 19 ++--- .../automation/IOBusPartChannelCardMixin.java | 70 +++++++++---------- .../IOBusPartTickerChannelCardMixin.java | 4 +- .../StorageBusPartChannelCardMixin.java | 64 ++++++++--------- .../StorageBusPartTickerChannelCardMixin.java | 4 +- .../mixin/hooks/ModelBakeryMixin.java | 2 +- .../EncodePatternTransferHandlerMixin.java | 2 +- 15 files changed, 120 insertions(+), 143 deletions(-) diff --git a/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java b/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java index 11b9013..023ee39 100644 --- a/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java +++ b/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java @@ -4,19 +4,19 @@ package com.extendedae_plus.bridge; * 非 mixin 包下的桥接接口,供 mixin 进行 instanceof 检测和回调。 */ public interface InterfaceWirelessLinkBridge { - void extendedae_plus$updateWirelessLink(); + void eap$updateWirelessLink(); /** * 获取无线连接状态(服务端返回真实状态,客户端返回同步状态) */ - default boolean extendedae_plus$isWirelessConnected() { + default boolean eap$isWirelessConnected() { return false; } /** * 设置客户端的无线连接状态(仅在客户端使用) */ - default void extendedae_plus$setClientWirelessState(boolean connected) { + default void eap$setClientWirelessState(boolean connected) { // 默认实现为空 } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java index 5e94289..9e84b7f 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java @@ -25,17 +25,17 @@ import java.util.List; public abstract class PatternProviderScreenUpgradesMixin extends AEBaseScreen { @Inject(method = "", at = @At("TAIL")) - private void extendedae_plus$initUpgrades(PatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { + private void eap$initUpgrades(PatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { this.widgets.add("upgrades", new UpgradesPanel( menu.getSlots(SlotSemantics.UPGRADE), - this::extendedae_plus$getCompatibleUpgrades)); + this::eap$getCompatibleUpgrades)); if (((IUpgradableMenu) menu).getToolbox().isPresent()) { this.widgets.add("toolbox", new ToolboxPanel(style, ((IUpgradableMenu) menu).getToolbox().getName())); } } @Unique - private List extendedae_plus$getCompatibleUpgrades() { + private List eap$getCompatibleUpgrades() { var list = new ArrayList(); list.add(GuiText.CompatibleUpgrades.text()); list.addAll(Upgrades.getTooltipLinesForMachine(((IUpgradableMenu) menu).getUpgrades().getUpgradableItem())); diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java index 659b120..b8e7401 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java @@ -28,23 +28,23 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles @Shadow(remap = false) protected InterfaceLogicHost host; @Unique - private WirelessSlaveLink extendedae_plus$link; + private WirelessSlaveLink eap$link; @Inject(method = "onUpgradesChanged", at = @At("TAIL"), remap = false) - private void extendedae_plus$onUpgradesChangedTail(CallbackInfo ci) { + private void eap$onUpgradesChangedTail(CallbackInfo ci) { handleChannelCardChange(); } @Inject(method = "readFromNBT", at = @At("TAIL"), remap = false) - private void extendedae_plus$afterReadNBT(net.minecraft.nbt.CompoundTag tag, CallbackInfo ci) { + private void eap$afterReadNBT(net.minecraft.nbt.CompoundTag tag, CallbackInfo ci) { // 重载后根据卡状态恢复连接 handleChannelCardChange(); } @Inject(method = "clearContent", at = @At("HEAD"), remap = false) - private void extendedae_plus$onClearContent(CallbackInfo ci) { - if (extendedae_plus$link != null) { - extendedae_plus$link.onUnloadOrRemove(); + private void eap$onClearContent(CallbackInfo ci) { + if (eap$link != null) { + eap$link.onUnloadOrRemove(); } } @@ -58,17 +58,17 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles break; } } - if (extendedae_plus$link == null) { + if (eap$link == null) { IWirelessEndpoint endpoint = new InterfaceNodeEndpointImpl(host, this::getActionableNode); - extendedae_plus$link = new WirelessSlaveLink(endpoint); + eap$link = new WirelessSlaveLink(endpoint); } - extendedae_plus$link.setFrequency(channel); + eap$link.setFrequency(channel); } @Override - public void extendedae_plus$updateWirelessLink() { - if (extendedae_plus$link != null) { - extendedae_plus$link.updateStatus(); + public void eap$updateWirelessLink() { + if (eap$link != null) { + eap$link.updateStatus(); } } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelLinkBridge.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelLinkBridge.java index 498d023..7dd6ad0 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelLinkBridge.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelLinkBridge.java @@ -4,5 +4,5 @@ package com.extendedae_plus.bridge; * 旧名兼容:已迁移到非 mixin 包,避免 Mixin 处理器禁止直接引用。 */ public interface InterfaceLogicChannelLinkBridge { - void extendedae_plus$updateWirelessLink(); + void eap$updateWirelessLink(); } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java index 77f9cbc..50ac299 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java @@ -21,10 +21,10 @@ public abstract class InterfaceLogicTickerMixin { private InterfaceLogic this$0; @Inject(method = "tickingRequest", at = @At("TAIL"), remap = false) - private void extendedae_plus$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall, + private void eap$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable cir) { if (this$0 instanceof InterfaceWirelessLinkBridge bridge) { - bridge.extendedae_plus$updateWirelessLink(); + bridge.eap$updateWirelessLink(); } } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java index 8baebff..8753adb 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java @@ -21,10 +21,10 @@ public abstract class PatternProviderLogicTickerMixin { private PatternProviderLogic this$0; @Inject(method = "tickingRequest", at = @At("TAIL")) - private void extendedae_plus$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall, + private void eap$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable cir) { if (this$0 instanceof InterfaceWirelessLinkBridge bridge) { - bridge.extendedae_plus$updateWirelessLink(); + bridge.eap$updateWirelessLink(); } } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java index 82e5bb0..7b56bae 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java @@ -32,10 +32,10 @@ import java.util.List; @Mixin(value = PatternProviderLogic.class, remap = false) public abstract class PatternProviderLogicUpgradesMixin implements IUpgradeableObject, InterfaceWirelessLinkBridge { @Unique - private IUpgradeInventory extendedae_plus$upgrades = UpgradeInventories.empty(); + private IUpgradeInventory eap$upgrades = UpgradeInventories.empty(); @Unique - private WirelessSlaveLink extendedae_plus$link; + private WirelessSlaveLink eap$link; @Final @Shadow @@ -50,48 +50,48 @@ public abstract class PatternProviderLogicUpgradesMixin implements IUpgradeableO private IActionSource actionSource; @Unique - private void extendedae_plus$onUpgradesChanged() { + private void eap$onUpgradesChanged() { this.host.saveChanges(); // 读取频道卡,更新无线链接频率 long channel = 0L; - for (var stack : this.extendedae_plus$upgrades) { + for (var stack : this.eap$upgrades) { if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { channel = ChannelCardItem.getChannel(stack); break; } } - if (extendedae_plus$link == null) { + if (eap$link == null) { var endpoint = new GenericNodeEndpointImpl(() -> host.getBlockEntity(), () -> this.mainNode.getNode()); - extendedae_plus$link = new WirelessSlaveLink(endpoint); + eap$link = new WirelessSlaveLink(endpoint); } - extendedae_plus$link.setFrequency(channel); - extendedae_plus$link.updateStatus(); + eap$link.setFrequency(channel); + eap$link.updateStatus(); } @Override public IUpgradeInventory getUpgrades() { - return this.extendedae_plus$upgrades; + return this.eap$upgrades; } @Inject(method = "(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/patternprovider/PatternProviderLogicHost;I)V", at = @At("TAIL")) - private void extendedae_plus$initUpgrades(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) { - this.extendedae_plus$upgrades = UpgradeInventories.forMachine(host.getTerminalIcon().getItem(), 1, this::extendedae_plus$onUpgradesChanged); + private void eap$initUpgrades(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) { + this.eap$upgrades = UpgradeInventories.forMachine(host.getTerminalIcon().getItem(), 1, this::eap$onUpgradesChanged); } @Inject(method = "writeToNBT", at = @At("TAIL")) - private void extendedae_plus$saveUpgrades(CompoundTag tag, CallbackInfo ci) { - this.extendedae_plus$upgrades.writeToNBT(tag, "upgrades"); + private void eap$saveUpgrades(CompoundTag tag, CallbackInfo ci) { + this.eap$upgrades.writeToNBT(tag, "upgrades"); } @Inject(method = "readFromNBT", at = @At("TAIL")) - private void extendedae_plus$loadUpgrades(CompoundTag tag, CallbackInfo ci) { - this.extendedae_plus$upgrades.readFromNBT(tag, "upgrades"); + private void eap$loadUpgrades(CompoundTag tag, CallbackInfo ci) { + this.eap$upgrades.readFromNBT(tag, "upgrades"); } @Inject(method = "addDrops", at = @At("TAIL")) - private void extendedae_plus$dropUpgrades(List drops, CallbackInfo ci) { - for (var stack : this.extendedae_plus$upgrades) { + private void eap$dropUpgrades(List drops, CallbackInfo ci) { + for (var stack : this.eap$upgrades) { if (!stack.isEmpty()) { drops.add(stack); } @@ -99,14 +99,14 @@ public abstract class PatternProviderLogicUpgradesMixin implements IUpgradeableO } @Inject(method = "clearContent", at = @At("TAIL")) - private void extendedae_plus$clearUpgrades(CallbackInfo ci) { - this.extendedae_plus$upgrades.clear(); + private void eap$clearUpgrades(CallbackInfo ci) { + this.eap$upgrades.clear(); } @Override - public void extendedae_plus$updateWirelessLink() { - if (extendedae_plus$link != null) { - extendedae_plus$link.updateStatus(); + public void eap$updateWirelessLink() { + if (eap$link != null) { + eap$link.updateStatus(); } } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java index 514b523..0cda3ae 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java @@ -24,18 +24,18 @@ public abstract class PatternProviderMenuUpgradesMixin extends AEBaseMenu implem @Shadow protected PatternProviderLogic logic; @Unique - private ToolboxMenu extendedae_plus$toolbox; + private ToolboxMenu eap$toolbox; @Inject(method = "(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL")) - private void extendedae_plus$initUpgrades(MenuType menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) { - this.extendedae_plus$toolbox = new ToolboxMenu(this); + private void eap$initUpgrades(MenuType menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) { + this.eap$toolbox = new ToolboxMenu(this); this.setupUpgrades(((IUpgradeableObject) host).getUpgrades()); } @Override public ToolboxMenu getToolbox() { - return this.extendedae_plus$toolbox; + return this.eap$toolbox; } @Override diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/AEBasePartClientSyncMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/AEBasePartClientSyncMixin.java index 2ae0175..b180e30 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/parts/AEBasePartClientSyncMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/AEBasePartClientSyncMixin.java @@ -16,7 +16,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; public class AEBasePartClientSyncMixin { @Inject(method = "writeToStream", at = @At("TAIL")) - private void extendedae_plus$writeWirelessState(FriendlyByteBuf data, CallbackInfo ci) { + private void eap$writeWirelessState(FriendlyByteBuf data, CallbackInfo ci) { // 检查是否实现了无线链接桥接接口 if (this instanceof InterfaceWirelessLinkBridge) { InterfaceWirelessLinkBridge bridge = (InterfaceWirelessLinkBridge) this; @@ -26,15 +26,10 @@ public class AEBasePartClientSyncMixin { // 只在服务端获取真实连接状态 AEBasePart part = (AEBasePart)(Object)this; if (!part.isClientSide()) { - connected = bridge.extendedae_plus$isWirelessConnected(); - // 调试日志:记录写入的状态 - if (part.getClass().getSimpleName().contains("IOBus")) { - System.out.println("[写入状态] IOBus 无线连接状态: " + connected); - } + connected = bridge.eap$isWirelessConnected(); } } catch (Exception e) { // 忽略异常,默认为false - System.err.println("获取无线连接状态失败: " + e.getMessage()); } data.writeBoolean(connected); } else { @@ -44,7 +39,7 @@ public class AEBasePartClientSyncMixin { } @Inject(method = "readFromStream", at = @At("TAIL")) - private void extendedae_plus$readWirelessState(FriendlyByteBuf data, CallbackInfoReturnable cir) { + private void eap$readWirelessState(FriendlyByteBuf data, CallbackInfoReturnable cir) { // 读取无线连接状态 boolean connected = data.readBoolean(); @@ -52,16 +47,10 @@ public class AEBasePartClientSyncMixin { if (this instanceof InterfaceWirelessLinkBridge) { InterfaceWirelessLinkBridge bridge = (InterfaceWirelessLinkBridge) this; try { - // 调试日志:记录读取的状态 - AEBasePart part = (AEBasePart)(Object)this; - if (part.getClass().getSimpleName().contains("IOBus")) { - System.out.println("[读取状态] IOBus 无线连接状态: " + connected); - } // 更新客户端状态 - bridge.extendedae_plus$setClientWirelessState(connected); + bridge.eap$setClientWirelessState(connected); } catch (Exception e) { // 忽略异常 - System.err.println("设置客户端无线连接状态失败: " + e.getMessage()); } } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java index cf21f1d..b09b40d 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java @@ -26,45 +26,45 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLinkBridge, IUpgradeableObject { @Unique - private WirelessSlaveLink extendedae_plus$link; + private WirelessSlaveLink eap$link; @Unique - private long extendedae_plus$lastChannel = -1; + private long eap$lastChannel = -1; @Unique - private boolean extendedae_plus$clientConnected = false; + private boolean eap$clientConnected = false; @Unique - private boolean extendedae_plus$hasTickInitialized = false; + private boolean eap$hasTickInitialized = false; @Inject(method = "upgradesChanged", at = @At("TAIL")) - private void extendedae_plus$onUpgradesChanged(CallbackInfo ci) { + private void eap$onUpgradesChanged(CallbackInfo ci) { // 只在服务端初始化频道链接 if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) { - extendedae_plus$initializeChannelLink(); + eap$initializeChannelLink(); } } @Inject(method = "tickingRequest", at = @At("HEAD")) - private void extendedae_plus$beforeTick(appeng.api.networking.IGridNode node, int ticksSinceLastCall, org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable cir) { + private void eap$beforeTick(appeng.api.networking.IGridNode node, int ticksSinceLastCall, org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable cir) { // 在第一次tick时初始化频道链接(此时网格节点已经在线) - if (!extendedae_plus$hasTickInitialized && !((appeng.parts.AEBasePart)(Object)this).isClientSide()) { - extendedae_plus$hasTickInitialized = true; - extendedae_plus$initializeChannelLink(); + if (!eap$hasTickInitialized && !((appeng.parts.AEBasePart)(Object)this).isClientSide()) { + eap$hasTickInitialized = true; + eap$initializeChannelLink(); } } @Inject(method = "readFromNBT", at = @At("TAIL")) - private void extendedae_plus$afterReadFromNBT(CompoundTag extra, CallbackInfo ci) { + private void eap$afterReadFromNBT(CompoundTag extra, CallbackInfo ci) { // 从NBT加载时重置频道缓存和tick初始化标志 if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) { - extendedae_plus$lastChannel = -1; - extendedae_plus$hasTickInitialized = false; // 重置标志,允许再次初始化 + eap$lastChannel = -1; + eap$hasTickInitialized = false; // 重置标志,允许再次初始化 } } @Unique - private void extendedae_plus$initializeChannelLink() { + private void eap$initializeChannelLink() { // 防止重复调用 if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) { return; @@ -83,18 +83,18 @@ public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLink } // 频道没有变化则跳过 - if (extendedae_plus$lastChannel == channel) { + if (eap$lastChannel == channel) { return; } - extendedae_plus$lastChannel = channel; + eap$lastChannel = channel; ExtendedAELogger.LOGGER.debug("[服务端] IOBus 初始化频道链接: found={}, channel={}", found, channel); if (!found) { // 无频道卡则断开 - if (extendedae_plus$link != null) { - extendedae_plus$link.setFrequency(0L); - extendedae_plus$link.updateStatus(); + if (eap$link != null) { + eap$link.setFrequency(0L); + eap$link.updateStatus(); ExtendedAELogger.LOGGER.debug("[服务端] IOBus 断开频道链接"); // 立即通知客户端状态变化(断开连接无需延迟) ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate(); @@ -102,24 +102,18 @@ public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLink return; } - if (extendedae_plus$link == null) { + if (eap$link == null) { var endpoint = new GenericNodeEndpointImpl( () -> ((appeng.parts.AEBasePart)(Object)this).getHost().getBlockEntity(), () -> ((IActionHost)(Object)this).getActionableNode() ); - extendedae_plus$link = new WirelessSlaveLink(endpoint); + eap$link = new WirelessSlaveLink(endpoint); ExtendedAELogger.LOGGER.debug("[服务端] IOBus 创建新的无线链接"); } - extendedae_plus$link.setFrequency(channel); - extendedae_plus$link.updateStatus(); - - // 调试信息:检查网格节点状态 - var gridNode = ((IActionHost)(Object)this).getActionableNode(); - ExtendedAELogger.LOGGER.debug("[服务端] IOBus 设置频道: {}, 连接状态: {}, 网格节点: {}, 在线: {}", - channel, extendedae_plus$link.isConnected(), - gridNode != null ? "exists" : "null", - gridNode != null ? gridNode.isOnline() : "N/A"); + eap$link.setFrequency(channel); + eap$link.updateStatus(); + ExtendedAELogger.LOGGER.debug("[服务端] IOBus 设置频道: {}, 连接状态: {}", channel, eap$link.isConnected()); // 通知客户端状态变化 ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate(); @@ -129,23 +123,23 @@ public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLink } @Override - public void extendedae_plus$updateWirelessLink() { - if (extendedae_plus$link != null) { - extendedae_plus$link.updateStatus(); + public void eap$updateWirelessLink() { + if (eap$link != null) { + eap$link.updateStatus(); } } @Override - public boolean extendedae_plus$isWirelessConnected() { + public boolean eap$isWirelessConnected() { if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) { - return extendedae_plus$clientConnected; + return eap$clientConnected; } else { - return extendedae_plus$link != null && extendedae_plus$link.isConnected(); + return eap$link != null && eap$link.isConnected(); } } @Override - public void extendedae_plus$setClientWirelessState(boolean connected) { - extendedae_plus$clientConnected = connected; + public void eap$setClientWirelessState(boolean connected) { + eap$clientConnected = connected; } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartTickerChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartTickerChannelCardMixin.java index c5bd28f..d9ac8ba 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartTickerChannelCardMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartTickerChannelCardMixin.java @@ -16,9 +16,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; public abstract class IOBusPartTickerChannelCardMixin { @Inject(method = "tickingRequest", at = @At("TAIL")) - private void extendedae_plus$tickTail(IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable cir) { + private void eap$tickTail(IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable cir) { if (this instanceof InterfaceWirelessLinkBridge bridge) { - bridge.extendedae_plus$updateWirelessLink(); + bridge.eap$updateWirelessLink(); } } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java index e4cc287..00b6626 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java @@ -26,42 +26,42 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; public abstract class StorageBusPartChannelCardMixin implements InterfaceWirelessLinkBridge, IUpgradeableObject { @Unique - private WirelessSlaveLink extendedae_plus$link; + private WirelessSlaveLink eap$link; @Unique - private long extendedae_plus$lastChannel = -1; + private long eap$lastChannel = -1; @Unique - private boolean extendedae_plus$clientConnected = false; + private boolean eap$clientConnected = false; @Inject(method = "upgradesChanged", at = @At("TAIL")) - private void extendedae_plus$onUpgradesChanged(CallbackInfo ci) { + private void eap$onUpgradesChanged(CallbackInfo ci) { // 只在服务端初始化频道链接 if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) { - extendedae_plus$initializeChannelLink(); + eap$initializeChannelLink(); } } @Inject(method = "onMainNodeStateChanged", at = @At("TAIL")) - private void extendedae_plus$onMainNodeStateChanged(IGridNodeListener.State reason, CallbackInfo ci) { + private void eap$onMainNodeStateChanged(IGridNodeListener.State reason, CallbackInfo ci) { // 在节点状态变化时(包括加载后的GRID_BOOT)重新初始化频道链接 if (reason == IGridNodeListener.State.GRID_BOOT && !((appeng.parts.AEBasePart)(Object)this).isClientSide()) { - extendedae_plus$initializeChannelLink(); + eap$initializeChannelLink(); } } @Inject(method = "readFromNBT", at = @At("TAIL")) - private void extendedae_plus$afterReadFromNBT(CompoundTag extra, CallbackInfo ci) { + private void eap$afterReadFromNBT(CompoundTag extra, CallbackInfo ci) { // 从NBT加载后也重新初始化频道链接(只在服务端) if (!((appeng.parts.AEBasePart)(Object)this).isClientSide()) { // 从NBT加载时重置频道缓存,强制重新初始化 - extendedae_plus$lastChannel = -1; - extendedae_plus$initializeChannelLink(); + eap$lastChannel = -1; + eap$initializeChannelLink(); } } @Unique - private void extendedae_plus$initializeChannelLink() { + private void eap$initializeChannelLink() { // 防止重复调用 if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) { return; @@ -80,17 +80,17 @@ public abstract class StorageBusPartChannelCardMixin implements InterfaceWireles } // 频道没有变化则跳过 - if (extendedae_plus$lastChannel == channel) { + if (eap$lastChannel == channel) { return; } - extendedae_plus$lastChannel = channel; + eap$lastChannel = channel; ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 初始化频道链接: found={}, channel={}", found, channel); if (!found) { - if (extendedae_plus$link != null) { - extendedae_plus$link.setFrequency(0L); - extendedae_plus$link.updateStatus(); + if (eap$link != null) { + eap$link.setFrequency(0L); + eap$link.updateStatus(); ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 断开频道链接"); // 通知客户端状态变化 ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate(); @@ -98,24 +98,18 @@ public abstract class StorageBusPartChannelCardMixin implements InterfaceWireles return; } - if (extendedae_plus$link == null) { + if (eap$link == null) { var endpoint = new GenericNodeEndpointImpl( () -> ((appeng.parts.AEBasePart)(Object)this).getHost().getBlockEntity(), () -> ((IActionHost)(Object)this).getActionableNode() ); - extendedae_plus$link = new WirelessSlaveLink(endpoint); + eap$link = new WirelessSlaveLink(endpoint); ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 创建新的无线链接"); } - extendedae_plus$link.setFrequency(channel); - extendedae_plus$link.updateStatus(); - - // 调试信息:检查网格节点状态 - var gridNode = ((IActionHost)(Object)this).getActionableNode(); - ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 设置频道: {}, 连接状态: {}, 网格节点: {}, 在线: {}", - channel, extendedae_plus$link.isConnected(), - gridNode != null ? "exists" : "null", - gridNode != null ? gridNode.isOnline() : "N/A"); + eap$link.setFrequency(channel); + eap$link.updateStatus(); + ExtendedAELogger.LOGGER.debug("[服务端] StorageBus 设置频道: {}, 连接状态: {}", channel, eap$link.isConnected()); // 通知客户端状态变化 ((appeng.parts.AEBasePart)(Object)this).getHost().markForUpdate(); @@ -125,23 +119,23 @@ public abstract class StorageBusPartChannelCardMixin implements InterfaceWireles } @Override - public void extendedae_plus$updateWirelessLink() { - if (extendedae_plus$link != null) { - extendedae_plus$link.updateStatus(); + public void eap$updateWirelessLink() { + if (eap$link != null) { + eap$link.updateStatus(); } } @Override - public boolean extendedae_plus$isWirelessConnected() { + public boolean eap$isWirelessConnected() { if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) { - return extendedae_plus$clientConnected; + return eap$clientConnected; } else { - return extendedae_plus$link != null && extendedae_plus$link.isConnected(); + return eap$link != null && eap$link.isConnected(); } } @Override - public void extendedae_plus$setClientWirelessState(boolean connected) { - extendedae_plus$clientConnected = connected; + public void eap$setClientWirelessState(boolean connected) { + eap$clientConnected = connected; } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartTickerChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartTickerChannelCardMixin.java index 636b4e4..202bc80 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartTickerChannelCardMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartTickerChannelCardMixin.java @@ -16,9 +16,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; public abstract class StorageBusPartTickerChannelCardMixin { @Inject(method = "tickingRequest", at = @At("TAIL")) - private void extendedae_plus$tickTail(IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable cir) { + private void eap$tickTail(IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable cir) { if (this instanceof InterfaceWirelessLinkBridge bridge) { - bridge.extendedae_plus$updateWirelessLink(); + bridge.eap$updateWirelessLink(); } } } diff --git a/src/main/java/com/extendedae_plus/mixin/hooks/ModelBakeryMixin.java b/src/main/java/com/extendedae_plus/mixin/hooks/ModelBakeryMixin.java index efc1b57..fa39a01 100644 --- a/src/main/java/com/extendedae_plus/mixin/hooks/ModelBakeryMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/hooks/ModelBakeryMixin.java @@ -19,7 +19,7 @@ import com.extendedae_plus.hooks.BuiltInModelHooks; @Mixin(ModelBakery.class) public class ModelBakeryMixin { @Inject(method = "loadModel", at = @At("HEAD"), cancellable = true) - private void extendedae_plus$loadModelHook(ResourceLocation id, CallbackInfo ci) { + private void eap$loadModelHook(ResourceLocation id, CallbackInfo ci) { var model = BuiltInModelHooks.getBuiltInModel(id); if (model != null) { cacheAndQueueDependencies(id, model); diff --git a/src/main/java/com/extendedae_plus/mixin/jei/EncodePatternTransferHandlerMixin.java b/src/main/java/com/extendedae_plus/mixin/jei/EncodePatternTransferHandlerMixin.java index 820b3ce..48e4b3f 100644 --- a/src/main/java/com/extendedae_plus/mixin/jei/EncodePatternTransferHandlerMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/jei/EncodePatternTransferHandlerMixin.java @@ -20,7 +20,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; public abstract class EncodePatternTransferHandlerMixin { @Inject(method = "transferRecipe", at = @At("HEAD"), require = 0) - private void extendedae_plus$captureProcessingName(PatternEncodingTermMenu menu, + private void eap$captureProcessingName(PatternEncodingTermMenu menu, Object recipeBase, IRecipeSlotsView slotsView, Player player, From b2b2740864d2d323bd09feedf75e83c0b252f4bd Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Tue, 23 Sep 2025 12:17:13 +0800 Subject: [PATCH 19/34] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dinterface=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bridge/InterfaceWirelessLinkBridge.java | 28 +++ .../InterfaceLogicChannelCardMixin.java | 215 ++++++++++++++++-- .../helpers/InterfaceLogicTickerMixin.java | 15 ++ .../automation/IOBusPartChannelCardMixin.java | 2 +- .../StorageBusPartChannelCardMixin.java | 2 +- 5 files changed, 245 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java b/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java index 023ee39..d200f77 100644 --- a/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java +++ b/src/main/java/com/extendedae_plus/bridge/InterfaceWirelessLinkBridge.java @@ -19,4 +19,32 @@ public interface InterfaceWirelessLinkBridge { default void eap$setClientWirelessState(boolean connected) { // 默认实现为空 } + + /** + * 检查是否已经进行过tick初始化 + */ + default boolean eap$hasTickInitialized() { + return true; // 默认认为已初始化 + } + + /** + * 设置tick初始化状态 + */ + default void eap$setTickInitialized(boolean initialized) { + // 默认实现为空 + } + + /** + * 执行频道链接初始化 + */ + default void eap$initializeChannelLink() { + // 默认实现为空 + } + + /** + * 检查并处理延迟初始化 + */ + default void eap$handleDelayedInit() { + // 默认实现为空 + } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java index b8e7401..39cabad 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java @@ -6,6 +6,7 @@ import appeng.helpers.InterfaceLogicHost; import com.extendedae_plus.ae.items.ChannelCardItem; import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; import com.extendedae_plus.init.ModItems; +import com.extendedae_plus.util.ExtendedAELogger; import com.extendedae_plus.wireless.IWirelessEndpoint; import com.extendedae_plus.wireless.WirelessSlaveLink; import com.extendedae_plus.wireless.endpoint.InterfaceNodeEndpointImpl; @@ -26,19 +27,63 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles @Shadow(remap = false) public abstract appeng.api.networking.IGridNode getActionableNode(); @Shadow(remap = false) protected InterfaceLogicHost host; + + @Shadow(remap = false) protected appeng.api.networking.IManagedGridNode mainNode; @Unique private WirelessSlaveLink eap$link; + + @Unique + private long eap$lastChannel = -1; + + @Unique + private boolean eap$clientConnected = false; + + @Unique + private boolean eap$hasInitialized = false; + + @Unique + private int eap$delayedInitTicks = 0; + + static { + ExtendedAELogger.LOGGER.info("[服务端] InterfaceLogicChannelCardMixin 已加载"); + } @Inject(method = "onUpgradesChanged", at = @At("TAIL"), remap = false) private void eap$onUpgradesChangedTail(CallbackInfo ci) { - handleChannelCardChange(); + // 升级变更时重置标志并尝试初始化 + eap$lastChannel = -1; + eap$hasInitialized = false; + eap$initializeChannelLink(); + } + + @Inject(method = "gridChanged", at = @At("TAIL"), remap = false) + private void eap$afterGridChanged(CallbackInfo ci) { + // 网格状态变化时重置标志并设置延迟初始化 + ExtendedAELogger.LOGGER.debug("[服务端] Interface gridChanged 触发,设置延迟初始化"); + eap$lastChannel = -1; + eap$hasInitialized = false; + eap$delayedInitTicks = 10; // 适当增加延迟tick,等待网格完成引导 + // 尝试唤醒设备,确保后续还能继续tick + if (mainNode != null) { + mainNode.ifPresent((grid, node) -> { + try { + grid.getTickManager().wakeDevice(node); + } catch (Throwable t) { + // 防御性日志,避免因这里的异常影响主流程 + ExtendedAELogger.LOGGER.debug("[服务端] Interface 唤醒设备失败: {}", t.toString()); + } + }); + } } @Inject(method = "readFromNBT", at = @At("TAIL"), remap = false) private void eap$afterReadNBT(net.minecraft.nbt.CompoundTag tag, CallbackInfo ci) { - // 重载后根据卡状态恢复连接 - handleChannelCardChange(); + // 从 NBT加载时重置标志 + eap$lastChannel = -1; + eap$hasInitialized = false; + // 直接尝试初始化 + eap$initializeChannelLink(); } @Inject(method = "clearContent", at = @At("HEAD"), remap = false) @@ -49,20 +94,95 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles } @Unique - private void handleChannelCardChange() { - var inv = getUpgrades(); - long channel = 0L; - for (ItemStack stack : inv) { - if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { - channel = ChannelCardItem.getChannel(stack); - break; + public void eap$initializeChannelLink() { + ExtendedAELogger.LOGGER.debug("[服务端] Interface eap$initializeChannelLink 被调用"); + + // 防止在客户端执行 + if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) { + ExtendedAELogger.LOGGER.debug("[服务端] Interface 在客户端,跳过初始化"); + return; + } + + // 检查是否已经初始化过 + if (eap$hasInitialized) { + ExtendedAELogger.LOGGER.debug("[服务端] Interface 已经初始化过,跳过"); + return; + } + + // 优先等待网格完成引导(比仅检查 isActive 更可靠) + if (!mainNode.hasGridBooted()) { + ExtendedAELogger.LOGGER.debug("[服务端] Interface 网格未完成引导(boot),等待后再初始化: ready={}, active={}, online={}", + mainNode.isReady(), mainNode.isActive(), mainNode.isOnline()); + return; + } + + try { + var inv = getUpgrades(); + long channel = 0L; + boolean found = false; + for (ItemStack stack : inv) { + if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { + channel = ChannelCardItem.getChannel(stack); + found = true; + break; + } } + + ExtendedAELogger.LOGGER.debug("[服务端] Interface 初始化频道链接: found={}, channel={}", found, channel); + + if (!found) { + // 无频道卡则断开 + if (eap$link != null) { + eap$link.setFrequency(0L); + eap$link.updateStatus(); + ExtendedAELogger.LOGGER.debug("[服务端] Interface 断开频道链接"); + } + eap$hasInitialized = true; // 无频道卡也算初始化完成 + return; + } + + if (eap$link == null) { + // 使用mainNode而不是getActionableNode,因为后者可能返回null + IWirelessEndpoint endpoint = new InterfaceNodeEndpointImpl(host, () -> mainNode.getNode()); + eap$link = new WirelessSlaveLink(endpoint); + ExtendedAELogger.LOGGER.debug("[服务端] Interface 创建新的无线链接"); + } + + eap$link.setFrequency(channel); + eap$link.updateStatus(); + + // 调试信息:检查网格节点状态 + var gridNode = mainNode.getNode(); + var isActive = mainNode.isActive(); + ExtendedAELogger.LOGGER.debug("[服务端] Interface 设置频道: {}, 连接状态: {}, 网格节点: {}, 激活: {}, 在线: {}", + channel, eap$link.isConnected(), + gridNode != null ? "exists" : "null", + isActive, + gridNode != null ? gridNode.isOnline() : "N/A"); + + if (eap$link.isConnected()) { + eap$hasInitialized = true; // 设置初始化完成标志 + ExtendedAELogger.LOGGER.debug("[服务端] Interface 无线链接建立成功"); + } else { + ExtendedAELogger.LOGGER.warn("[服务端] Interface 无线链接建立失败,将继续重试"); + // 不标记为完成,允许后续tick重试 + eap$hasInitialized = false; + // 设置一个短延迟窗口,避免每tick刷屏 + eap$delayedInitTicks = Math.max(eap$delayedInitTicks, 5); + try { + mainNode.ifPresent((grid, node) -> { + try { + grid.getTickManager().wakeDevice(node); + } catch (Throwable t) { + ExtendedAELogger.LOGGER.debug("[服务端] Interface 初始化失败后唤醒设备失败: {}", t.toString()); + } + }); + } catch (Throwable ignored) { + } + } + } catch (Exception e) { + ExtendedAELogger.LOGGER.error("[服务端] Interface 初始化频道链接失败", e); } - if (eap$link == null) { - IWirelessEndpoint endpoint = new InterfaceNodeEndpointImpl(host, this::getActionableNode); - eap$link = new WirelessSlaveLink(endpoint); - } - eap$link.setFrequency(channel); } @Override @@ -71,4 +191,69 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles eap$link.updateStatus(); } } + + @Override + public boolean eap$isWirelessConnected() { + // InterfaceLogic没有isClientSide方法,需要通过host判断 + if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) { + return eap$clientConnected; + } else { + return eap$link != null && eap$link.isConnected(); + } + } + + @Override + public void eap$setClientWirelessState(boolean connected) { + eap$clientConnected = connected; + } + + @Override + public boolean eap$hasTickInitialized() { + return eap$hasInitialized; + } + + @Override + public void eap$setTickInitialized(boolean initialized) { + eap$hasInitialized = initialized; + } + + @Override + public void eap$handleDelayedInit() { + // 仅在服务端执行延迟初始化,避免在渲染线程/客户端触发任何初始化路径 + if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) { + return; + } + + // 若尚未初始化,则持续尝试,直到网格完成引导 + if (!eap$hasInitialized) { + if (!mainNode.hasGridBooted()) { + // 仍在引导,消耗计时器 + if (eap$delayedInitTicks > 0) { + eap$delayedInitTicks--; + } + if (eap$delayedInitTicks == 0) { + // 重新设定一个短延迟窗口,并唤醒设备,以保证后续还能继续 tick + eap$delayedInitTicks = 5; + try { + mainNode.ifPresent((grid, node) -> { + try { + grid.getTickManager().wakeDevice(node); + } catch (Throwable t) { + ExtendedAELogger.LOGGER.debug("[服务端] Interface 延迟等待期间唤醒设备失败: {}", t.toString()); + } + }); + } catch (Throwable ignored) { + } + ExtendedAELogger.LOGGER.debug("[服务端] Interface 网格仍在引导,继续等待: ready={}, active={}, online={}", + mainNode.isReady(), mainNode.isActive(), mainNode.isOnline()); + } + } else { + // 网格已引导完成,执行初始化 + ExtendedAELogger.LOGGER.debug("[服务端] Interface 延迟初始化触发(网格已完成引导)"); + eap$initializeChannelLink(); + } + } + } + + // eap$initializeChannelLink方法已在上面实现 } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java index 50ac299..a2063d5 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicTickerMixin.java @@ -2,6 +2,7 @@ package com.extendedae_plus.mixin.ae2.helpers; import appeng.helpers.InterfaceLogic; import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import com.extendedae_plus.util.ExtendedAELogger; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -20,6 +21,20 @@ public abstract class InterfaceLogicTickerMixin { @Final private InterfaceLogic this$0; + @Inject(method = "tickingRequest", at = @At("HEAD"), remap = false) + private void eap$tickHead(appeng.api.networking.IGridNode node, int ticksSinceLastCall, + CallbackInfoReturnable cir) { + // 仅在服务端处理延迟初始化,避免客户端干扰 + if (node != null && node.getLevel() != null && node.getLevel().isClientSide) { + return; + } + + if (this$0 instanceof InterfaceWirelessLinkBridge bridge) { + // 处理延迟初始化 + bridge.eap$handleDelayedInit(); + } + } + @Inject(method = "tickingRequest", at = @At("TAIL"), remap = false) private void eap$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable cir) { diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java index b09b40d..b00c3e1 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/automation/IOBusPartChannelCardMixin.java @@ -64,7 +64,7 @@ public abstract class IOBusPartChannelCardMixin implements InterfaceWirelessLink } @Unique - private void eap$initializeChannelLink() { + public void eap$initializeChannelLink() { // 防止重复调用 if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) { return; diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java index 00b6626..50e4a71 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/parts/storagebus/StorageBusPartChannelCardMixin.java @@ -61,7 +61,7 @@ public abstract class StorageBusPartChannelCardMixin implements InterfaceWireles } @Unique - private void eap$initializeChannelLink() { + public void eap$initializeChannelLink() { // 防止重复调用 if (((appeng.parts.AEBasePart)(Object)this).isClientSide()) { return; From 773d1bb26378ac4b218bb49fd24131cbc99adaa2 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Tue, 23 Sep 2025 12:48:50 +0800 Subject: [PATCH 20/34] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=A0=B7=E6=9D=BF?= =?UTF-8?q?=E4=BE=9B=E5=BA=94=E5=99=A8=E5=88=9D=E5=A7=8B=E5=8C=96=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InterfaceLogicChannelCardMixin.java | 68 ++----- .../PatternProviderLogicTickerMixin.java | 12 ++ .../PatternProviderLogicUpgradesMixin.java | 175 ++++++++++++++++-- 3 files changed, 186 insertions(+), 69 deletions(-) diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java index 39cabad..bb4b6cf 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/InterfaceLogicChannelCardMixin.java @@ -6,8 +6,6 @@ import appeng.helpers.InterfaceLogicHost; import com.extendedae_plus.ae.items.ChannelCardItem; import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; import com.extendedae_plus.init.ModItems; -import com.extendedae_plus.util.ExtendedAELogger; -import com.extendedae_plus.wireless.IWirelessEndpoint; import com.extendedae_plus.wireless.WirelessSlaveLink; import com.extendedae_plus.wireless.endpoint.InterfaceNodeEndpointImpl; import net.minecraft.server.level.ServerLevel; @@ -46,7 +44,7 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles private int eap$delayedInitTicks = 0; static { - ExtendedAELogger.LOGGER.info("[服务端] InterfaceLogicChannelCardMixin 已加载"); + // InterfaceLogicChannelCardMixin 已加载 } @Inject(method = "onUpgradesChanged", at = @At("TAIL"), remap = false) @@ -60,7 +58,6 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles @Inject(method = "gridChanged", at = @At("TAIL"), remap = false) private void eap$afterGridChanged(CallbackInfo ci) { // 网格状态变化时重置标志并设置延迟初始化 - ExtendedAELogger.LOGGER.debug("[服务端] Interface gridChanged 触发,设置延迟初始化"); eap$lastChannel = -1; eap$hasInitialized = false; eap$delayedInitTicks = 10; // 适当增加延迟tick,等待网格完成引导 @@ -69,9 +66,7 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles mainNode.ifPresent((grid, node) -> { try { grid.getTickManager().wakeDevice(node); - } catch (Throwable t) { - // 防御性日志,避免因这里的异常影响主流程 - ExtendedAELogger.LOGGER.debug("[服务端] Interface 唤醒设备失败: {}", t.toString()); + } catch (Throwable ignored) { } }); } @@ -95,76 +90,53 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles @Unique public void eap$initializeChannelLink() { - ExtendedAELogger.LOGGER.debug("[服务端] Interface eap$initializeChannelLink 被调用"); - - // 防止在客户端执行 + // 仅在服务端执行,避免在渲染线程/客户端触发任何初始化路径 if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) { - ExtendedAELogger.LOGGER.debug("[服务端] Interface 在客户端,跳过初始化"); return; } - - // 检查是否已经初始化过 + + // 避免重复初始化 if (eap$hasInitialized) { - ExtendedAELogger.LOGGER.debug("[服务端] Interface 已经初始化过,跳过"); return; } - + // 优先等待网格完成引导(比仅检查 isActive 更可靠) if (!mainNode.hasGridBooted()) { - ExtendedAELogger.LOGGER.debug("[服务端] Interface 网格未完成引导(boot),等待后再初始化: ready={}, active={}, online={}", - mainNode.isReady(), mainNode.isActive(), mainNode.isOnline()); return; } - + try { - var inv = getUpgrades(); long channel = 0L; boolean found = false; - for (ItemStack stack : inv) { + for (ItemStack stack : getUpgrades()) { if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { channel = ChannelCardItem.getChannel(stack); found = true; break; } } - - ExtendedAELogger.LOGGER.debug("[服务端] Interface 初始化频道链接: found={}, channel={}", found, channel); - + if (!found) { - // 无频道卡则断开 + // 无频道卡:断开并视为初始化完成 if (eap$link != null) { eap$link.setFrequency(0L); eap$link.updateStatus(); - ExtendedAELogger.LOGGER.debug("[服务端] Interface 断开频道链接"); } - eap$hasInitialized = true; // 无频道卡也算初始化完成 + eap$hasInitialized = true; return; } - + if (eap$link == null) { - // 使用mainNode而不是getActionableNode,因为后者可能返回null - IWirelessEndpoint endpoint = new InterfaceNodeEndpointImpl(host, () -> mainNode.getNode()); + var endpoint = new InterfaceNodeEndpointImpl(host, () -> this.mainNode.getNode()); eap$link = new WirelessSlaveLink(endpoint); - ExtendedAELogger.LOGGER.debug("[服务端] Interface 创建新的无线链接"); } - + eap$link.setFrequency(channel); eap$link.updateStatus(); - // 调试信息:检查网格节点状态 - var gridNode = mainNode.getNode(); - var isActive = mainNode.isActive(); - ExtendedAELogger.LOGGER.debug("[服务端] Interface 设置频道: {}, 连接状态: {}, 网格节点: {}, 激活: {}, 在线: {}", - channel, eap$link.isConnected(), - gridNode != null ? "exists" : "null", - isActive, - gridNode != null ? gridNode.isOnline() : "N/A"); - if (eap$link.isConnected()) { eap$hasInitialized = true; // 设置初始化完成标志 - ExtendedAELogger.LOGGER.debug("[服务端] Interface 无线链接建立成功"); } else { - ExtendedAELogger.LOGGER.warn("[服务端] Interface 无线链接建立失败,将继续重试"); // 不标记为完成,允许后续tick重试 eap$hasInitialized = false; // 设置一个短延迟窗口,避免每tick刷屏 @@ -173,15 +145,13 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles mainNode.ifPresent((grid, node) -> { try { grid.getTickManager().wakeDevice(node); - } catch (Throwable t) { - ExtendedAELogger.LOGGER.debug("[服务端] Interface 初始化失败后唤醒设备失败: {}", t.toString()); + } catch (Throwable ignored) { } }); } catch (Throwable ignored) { } } - } catch (Exception e) { - ExtendedAELogger.LOGGER.error("[服务端] Interface 初始化频道链接失败", e); + } catch (Exception ignored) { } } @@ -238,18 +208,14 @@ public abstract class InterfaceLogicChannelCardMixin implements InterfaceWireles mainNode.ifPresent((grid, node) -> { try { grid.getTickManager().wakeDevice(node); - } catch (Throwable t) { - ExtendedAELogger.LOGGER.debug("[服务端] Interface 延迟等待期间唤醒设备失败: {}", t.toString()); + } catch (Throwable ignored) { } }); } catch (Throwable ignored) { } - ExtendedAELogger.LOGGER.debug("[服务端] Interface 网格仍在引导,继续等待: ready={}, active={}, online={}", - mainNode.isReady(), mainNode.isActive(), mainNode.isOnline()); } } else { // 网格已引导完成,执行初始化 - ExtendedAELogger.LOGGER.debug("[服务端] Interface 延迟初始化触发(网格已完成引导)"); eap$initializeChannelLink(); } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java index 8753adb..e799173 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicTickerMixin.java @@ -20,6 +20,18 @@ public abstract class PatternProviderLogicTickerMixin { @Final private PatternProviderLogic this$0; + @Inject(method = "tickingRequest", at = @At("HEAD")) + private void eap$tickHead(appeng.api.networking.IGridNode node, int ticksSinceLastCall, + CallbackInfoReturnable cir) { + // 仅在服务端处理延迟初始化 + if (node != null && node.getLevel() != null && node.getLevel().isClientSide) { + return; + } + if (this$0 instanceof InterfaceWirelessLinkBridge bridge) { + bridge.eap$handleDelayedInit(); + } + } + @Inject(method = "tickingRequest", at = @At("TAIL")) private void eap$tickTail(appeng.api.networking.IGridNode node, int ticksSinceLastCall, CallbackInfoReturnable cir) { diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java index 7b56bae..1a802cd 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java @@ -37,6 +37,18 @@ public abstract class PatternProviderLogicUpgradesMixin implements IUpgradeableO @Unique private WirelessSlaveLink eap$link; + @Unique + private long eap$lastChannel = -1; + + @Unique + private boolean eap$clientConnected = false; + + @Unique + private boolean eap$hasInitialized = false; + + @Unique + private int eap$delayedInitTicks = 0; + @Final @Shadow private PatternProviderLogicHost host; @@ -52,26 +64,13 @@ public abstract class PatternProviderLogicUpgradesMixin implements IUpgradeableO @Unique private void eap$onUpgradesChanged() { this.host.saveChanges(); - // 读取频道卡,更新无线链接频率 - long channel = 0L; - for (var stack : this.eap$upgrades) { - if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { - channel = ChannelCardItem.getChannel(stack); - break; - } - } - if (eap$link == null) { - var endpoint = new GenericNodeEndpointImpl(() -> host.getBlockEntity(), () -> this.mainNode.getNode()); - eap$link = new WirelessSlaveLink(endpoint); - } - eap$link.setFrequency(channel); - eap$link.updateStatus(); + // 升级变更,重置并尝试初始化 + eap$lastChannel = -1; + eap$hasInitialized = false; + eap$initializeChannelLink(); } - @Override - public IUpgradeInventory getUpgrades() { - return this.eap$upgrades; - } + @Inject(method = "(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/patternprovider/PatternProviderLogicHost;I)V", at = @At("TAIL")) @@ -87,6 +86,10 @@ public abstract class PatternProviderLogicUpgradesMixin implements IUpgradeableO @Inject(method = "readFromNBT", at = @At("TAIL")) private void eap$loadUpgrades(CompoundTag tag, CallbackInfo ci) { this.eap$upgrades.readFromNBT(tag, "upgrades"); + // 从 NBT 加载后,重置并尝试初始化(可能刚进入世界) + eap$lastChannel = -1; + eap$hasInitialized = false; + eap$initializeChannelLink(); } @Inject(method = "addDrops", at = @At("TAIL")) @@ -109,4 +112,140 @@ public abstract class PatternProviderLogicUpgradesMixin implements IUpgradeableO eap$link.updateStatus(); } } + + @Override + public IUpgradeInventory getUpgrades() { + return this.eap$upgrades; + } + + // ===== 频道卡初始化与延迟重试(服务端) ===== + @Unique + public void eap$initializeChannelLink() { + // 客户端早退 + if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) { + return; + } + + // 避免重复初始化 + if (eap$hasInitialized) { + return; + } + + // 等待网格完成引导 + if (!mainNode.hasGridBooted()) { + // 安排短延迟,等待后续 tick 再试 + eap$delayedInitTicks = Math.max(eap$delayedInitTicks, 5); + try { + mainNode.ifPresent((grid, node) -> { + try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {} + }); + } catch (Throwable ignored) {} + return; + } + + try { + long channel = 0L; + boolean found = false; + for (ItemStack stack : this.eap$upgrades) { + if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { + channel = ChannelCardItem.getChannel(stack); + found = true; + break; + } + } + + if (!found) { + // 无频道卡:断开并视为初始化完成 + if (eap$link != null) { + eap$link.setFrequency(0L); + eap$link.updateStatus(); + } + eap$hasInitialized = true; + return; + } + + if (eap$link == null) { + var endpoint = new GenericNodeEndpointImpl(() -> host.getBlockEntity(), () -> this.mainNode.getNode()); + eap$link = new WirelessSlaveLink(endpoint); + } + + eap$link.setFrequency(channel); + eap$link.updateStatus(); + + if (eap$link.isConnected()) { + eap$hasInitialized = true; + } else { + eap$hasInitialized = false; + eap$delayedInitTicks = Math.max(eap$delayedInitTicks, 5); + try { + mainNode.ifPresent((grid, node) -> { + try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {} + }); + } catch (Throwable ignored) {} + } + } catch (Exception ignored) { + } + } + + @Override + public void eap$setClientWirelessState(boolean connected) { + eap$clientConnected = connected; + } + + @Override + public boolean eap$isWirelessConnected() { + if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) { + return eap$clientConnected; + } else { + return eap$link != null && eap$link.isConnected(); + } + } + + @Override + public boolean eap$hasTickInitialized() { + return eap$hasInitialized; + } + + @Override + public void eap$setTickInitialized(boolean initialized) { + eap$hasInitialized = initialized; + } + + @Override + public void eap$handleDelayedInit() { + // 仅服务端 + if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) { + return; + } + if (!eap$hasInitialized) { + if (!mainNode.hasGridBooted()) { + if (eap$delayedInitTicks > 0) { + eap$delayedInitTicks--; + } + if (eap$delayedInitTicks == 0) { + eap$delayedInitTicks = 5; + try { + mainNode.ifPresent((grid, node) -> { + try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {} + }); + } catch (Throwable ignored) {} + } + } else { + eap$initializeChannelLink(); + } + } + } + + // 当主节点状态变化(例如 GRID_BOOT 完成)时,安排一次延迟初始化并唤醒设备 + @Inject(method = "onMainNodeStateChanged", at = @At("TAIL")) + private void eap$onMainNodeStateChangedTail(CallbackInfo ci) { + eap$lastChannel = -1; + eap$hasInitialized = false; + eap$delayedInitTicks = 10; + try { + mainNode.ifPresent((grid, node) -> { + try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {} + }); + } catch (Throwable ignored) {} + } } From a8b9b8e7490edae86cb9263321d837061ac2569c Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Tue, 23 Sep 2025 16:22:18 +0800 Subject: [PATCH 21/34] =?UTF-8?q?=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 9c945b4..25fe6a4 100644 --- a/build.gradle +++ b/build.gradle @@ -81,7 +81,7 @@ dependencies { annotationProcessor "org.spongepowered:mixin:${mixin_version}:processor" - modCompileOnly "curse.maven:applied-flux-965012:${applied_flux_version}" + modImplementation "curse.maven:applied-flux-965012:6755986" modCompileOnly "curse.maven:mega-cells-622112:${mega_cells_version}" modCompileOnly "curse.maven:jade-324717:${jade_version}" From 428f4520b2a8d7c23e13c40417ca23e1a8658c19 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Tue, 23 Sep 2025 17:43:38 +0800 Subject: [PATCH 22/34] =?UTF-8?q?=E5=85=BC=E5=AE=B9applied=20flux?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- .../com/extendedae_plus/ExtendedAEPlus.java | 4 + .../compat/CompatibilityTest.java | 62 ++++ .../compat/UpgradeSlotCompat.java | 202 +++++++++++ .../mixin/MixinConditions.java | 79 ++++ .../PatternProviderScreenUpgradesMixin.java | 10 +- .../compat/PatternProviderCompatMixin.java | 69 ++++ .../PatternProviderLogicCompatMixin.java | 337 ++++++++++++++++++ .../PatternProviderLogicHostCompatMixin.java | 27 ++ .../PatternProviderScreenCompatMixin.java | 88 +++++ ...PatternProviderLogicHostUpgradesMixin.java | 5 +- .../PatternProviderLogicUpgradesMixin.java | 23 +- .../PatternProviderMenuUpgradesMixin.java | 14 +- .../resources/extendedae_plus.mixins.json | 9 +- 14 files changed, 921 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/extendedae_plus/compat/CompatibilityTest.java create mode 100644 src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java create mode 100644 src/main/java/com/extendedae_plus/mixin/MixinConditions.java create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderCompatMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicCompatMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicHostCompatMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderScreenCompatMixin.java diff --git a/build.gradle b/build.gradle index 25fe6a4..f22d7c4 100644 --- a/build.gradle +++ b/build.gradle @@ -81,7 +81,7 @@ dependencies { annotationProcessor "org.spongepowered:mixin:${mixin_version}:processor" - modImplementation "curse.maven:applied-flux-965012:6755986" + // modImplementation "curse.maven:applied-flux-965012:6755986" modCompileOnly "curse.maven:mega-cells-622112:${mega_cells_version}" modCompileOnly "curse.maven:jade-324717:${jade_version}" diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index 9d993f8..efc4a7a 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -4,6 +4,7 @@ import appeng.api.storage.StorageCells; import appeng.menu.locator.MenuLocators; import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler; import com.extendedae_plus.client.ClientRegistrar; +import com.extendedae_plus.compat.CompatibilityTest; import com.extendedae_plus.config.ModConfig; import com.extendedae_plus.init.*; import com.extendedae_plus.menu.locator.CuriosItemLocator; @@ -62,6 +63,9 @@ public class ExtendedAEPlus { private void commonSetup(final FMLCommonSetupEvent event) { StorageCells.addCellHandler(InfinityBigIntegerCellHandler.INSTANCE); + // 运行兼容性测试 + CompatibilityTest.testCompatibility(); + // 注册本模组网络通道与数据包 event.enqueueWork(() -> { // 注册升级卡 diff --git a/src/main/java/com/extendedae_plus/compat/CompatibilityTest.java b/src/main/java/com/extendedae_plus/compat/CompatibilityTest.java new file mode 100644 index 0000000..50b7687 --- /dev/null +++ b/src/main/java/com/extendedae_plus/compat/CompatibilityTest.java @@ -0,0 +1,62 @@ +package com.extendedae_plus.compat; + +import com.extendedae_plus.util.ExtendedAELogger; +import net.minecraftforge.fml.ModList; + +/** + * 兼容性测试类 + * 用于验证模组兼容性检测是否正常工作 + */ +public class CompatibilityTest { + + /** + * 测试模组兼容性检测 + */ + public static void testCompatibility() { + ExtendedAELogger.LOGGER.info("=== ExtendedAE_Plus 兼容性测试开始 ==="); + + // 测试appflux模组检测 + boolean appfluxExists = ModList.get().isLoaded("appflux"); + ExtendedAELogger.LOGGER.info("ExtendedAE-appflux模组检测结果: {}", appfluxExists ? "存在" : "不存在"); + + // 测试升级卡槽功能启用状态 + boolean shouldEnableUpgrades = UpgradeSlotCompat.shouldEnableUpgradeSlots(); + ExtendedAELogger.LOGGER.info("升级卡槽功能启用状态: {}", shouldEnableUpgrades ? "启用" : "禁用"); + + // 测试Screen升级面板添加状态 + boolean shouldAddPanel = UpgradeSlotCompat.shouldAddUpgradePanelToScreen(); + ExtendedAELogger.LOGGER.info("Screen升级面板添加状态: {}", shouldAddPanel ? "启用" : "禁用"); + + // 输出兼容性策略 + if (appfluxExists) { + ExtendedAELogger.LOGGER.info("兼容性策略: 检测到ExtendedAE-appflux模组,将使用其升级卡槽功能"); + } else { + ExtendedAELogger.LOGGER.info("兼容性策略: 未检测到ExtendedAE-appflux模组,将使用我们自己的升级卡槽功能"); + } + + ExtendedAELogger.LOGGER.info("=== ExtendedAE_Plus 兼容性测试完成 ==="); + } + + /** + * 获取兼容性状态报告 + */ + public static String getCompatibilityReport() { + boolean appfluxExists = ModList.get().isLoaded("appflux"); + boolean upgradesEnabled = UpgradeSlotCompat.shouldEnableUpgradeSlots(); + + StringBuilder report = new StringBuilder(); + report.append("ExtendedAE_Plus 兼容性报告:\n"); + report.append("- ExtendedAE-appflux模组: ").append(appfluxExists ? "已安装" : "未安装").append("\n"); + report.append("- 升级卡槽功能: ").append(upgradesEnabled ? "启用中" : "已禁用").append("\n"); + + if (appfluxExists && !upgradesEnabled) { + report.append("- 兼容性状态: 正常 (使用appflux的升级功能)\n"); + } else if (!appfluxExists && upgradesEnabled) { + report.append("- 兼容性状态: 正常 (使用我们的升级功能)\n"); + } else { + report.append("- 兼容性状态: 异常 (配置不一致)\n"); + } + + return report.toString(); + } +} diff --git a/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java b/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java new file mode 100644 index 0000000..6c39295 --- /dev/null +++ b/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java @@ -0,0 +1,202 @@ +package com.extendedae_plus.compat; + +import appeng.api.upgrades.IUpgradeInventory; +import appeng.api.upgrades.IUpgradeableObject; +import appeng.api.upgrades.UpgradeInventories; +import appeng.client.gui.style.ScreenStyle; +import appeng.client.gui.widgets.ToolboxPanel; +import appeng.client.gui.widgets.UpgradesPanel; +import appeng.core.localization.GuiText; +import appeng.helpers.patternprovider.PatternProviderLogicHost; +import appeng.menu.AEBaseMenu; +import appeng.menu.SlotSemantics; +import appeng.menu.ToolboxMenu; +import com.extendedae_plus.util.ExtendedAELogger; +import net.minecraft.network.chat.Component; +import net.minecraft.world.inventory.Slot; +import net.minecraftforge.fml.ModList; + +import java.util.ArrayList; +import java.util.List; + +/** + * 升级卡槽兼容性管理类 + * 检测ExtendedAE-appflux模组是否存在,如果存在则使用其升级卡槽功能 + * 否则使用我们自己的实现 + */ +public class UpgradeSlotCompat { + private static final String APPFLUX_MOD_ID = "appflux"; + + /** + * 检测是否应该启用我们的升级卡槽功能 + * @return true如果应该启用,false如果检测到appflux模组存在 + */ + public static boolean shouldEnableUpgradeSlots() { + boolean appfluxExists = ModList.get().isLoaded(APPFLUX_MOD_ID); + ExtendedAELogger.LOGGER.info("ExtendedAE-appflux模组检测: {}", appfluxExists ? "存在" : "不存在"); + + if (appfluxExists) { + ExtendedAELogger.LOGGER.info("检测到ExtendedAE-appflux模组,跳过我们的升级卡槽功能"); + return false; + } else { + ExtendedAELogger.LOGGER.info("未检测到ExtendedAE-appflux模组,启用我们的升级卡槽功能"); + return true; + } + } + + /** + * 检测是否应该在Screen中添加升级面板 + * @return true如果应该添加,false如果检测到appflux模组存在 + */ + public static boolean shouldAddUpgradePanelToScreen() { + return shouldEnableUpgradeSlots(); + } + + /** + * 初始化菜单升级功能(如果需要的话) + * @param menu 目标菜单 + * @param host 样板供应器逻辑主机 + * @return 是否成功初始化 + */ + public static boolean initMenuUpgrades(AEBaseMenu menu, PatternProviderLogicHost host) { + if (!shouldEnableUpgradeSlots()) { + return false; + } + + try { + // 创建工具箱菜单 + ToolboxMenu toolbox = new ToolboxMenu(menu); + + // 设置升级槽 + if (host instanceof IUpgradeableObject upgradeableHost) { + // 使用反射调用protected的setupUpgrades方法 + try { + var setupUpgradesMethod = AEBaseMenu.class.getDeclaredMethod("setupUpgrades", IUpgradeInventory.class); + setupUpgradesMethod.setAccessible(true); + setupUpgradesMethod.invoke(menu, upgradeableHost.getUpgrades()); + } catch (Exception e) { + ExtendedAELogger.LOGGER.error("反射调用setupUpgrades失败", e); + return false; + } + + // 使用反射或接口设置工具箱 + if (menu instanceof IUpgradeableMenuCompat compatMenu) { + compatMenu.setCompatToolbox(toolbox); + } + + ExtendedAELogger.LOGGER.debug("成功为PatternProviderMenu初始化升级功能"); + return true; + } + } catch (Exception e) { + ExtendedAELogger.LOGGER.error("初始化PatternProviderMenu升级功能时出错", e); + } + + return false; + } + + /** + * 为Screen添加升级面板(如果需要的话) + * @param widgets 小部件映射 + * @param menu 菜单实例 + * @param style 屏幕样式 + * @return 是否成功添加 + */ + public static boolean addUpgradePanelToScreen(Object widgets, Object menu, ScreenStyle style) { + if (!shouldAddUpgradePanelToScreen()) { + return false; + } + + try { + if (menu instanceof IUpgradeableMenuCompat compatMenu) { + try { + // 使用反射获取widgets的add方法 - 尝试不同的方法签名 + var widgetsClass = widgets.getClass(); + var addMethod = widgetsClass.getDeclaredMethod("add", String.class, Object.class); + addMethod.setAccessible(true); + + // 获取升级槽位 + var menuClass = menu.getClass(); + var getSlotsMethod = menuClass.getMethod("getSlots", SlotSemantics.class); + @SuppressWarnings("unchecked") + List upgradeSlots = (List) getSlotsMethod.invoke(menu, SlotSemantics.UPGRADE); + + // 添加升级面板 + UpgradesPanel upgradesPanel = new UpgradesPanel(upgradeSlots, () -> getCompatibleUpgrades(compatMenu)); + addMethod.invoke(widgets, "upgrades", upgradesPanel); + + // 添加工具箱面板(如果存在) + ToolboxMenu toolbox = compatMenu.getCompatToolbox(); + if (toolbox != null && toolbox.isPresent()) { + ToolboxPanel toolboxPanel = new ToolboxPanel(style, toolbox.getName()); + addMethod.invoke(widgets, "toolbox", toolboxPanel); + } + + ExtendedAELogger.LOGGER.debug("成功为PatternProviderScreen添加升级面板"); + return true; + } catch (NoSuchMethodException e) { + // 尝试其他可能的方法签名 + try { + var widgetsClass = widgets.getClass(); + var putMethod = widgetsClass.getDeclaredMethod("put", String.class, Object.class); + putMethod.setAccessible(true); + + // 获取升级槽位 + var menuClass = menu.getClass(); + var getSlotsMethod = menuClass.getMethod("getSlots", SlotSemantics.class); + @SuppressWarnings("unchecked") + List upgradeSlots = (List) getSlotsMethod.invoke(menu, SlotSemantics.UPGRADE); + + // 添加升级面板 + UpgradesPanel upgradesPanel = new UpgradesPanel(upgradeSlots, () -> getCompatibleUpgrades(compatMenu)); + putMethod.invoke(widgets, "upgrades", upgradesPanel); + + // 添加工具箱面板(如果存在) + ToolboxMenu toolbox = compatMenu.getCompatToolbox(); + if (toolbox != null && toolbox.isPresent()) { + ToolboxPanel toolboxPanel = new ToolboxPanel(style, toolbox.getName()); + putMethod.invoke(widgets, "toolbox", toolboxPanel); + } + + ExtendedAELogger.LOGGER.debug("成功为PatternProviderScreen添加升级面板(使用put方法)"); + return true; + } catch (Exception e2) { + ExtendedAELogger.LOGGER.error("反射调用widgets方法失败", e2); + return false; + } + } + } + } catch (Exception e) { + ExtendedAELogger.LOGGER.error("为PatternProviderScreen添加升级面板时出错", e); + } + + return false; + } + + /** + * 获取兼容的升级列表 + */ + private static List getCompatibleUpgrades(IUpgradeableMenuCompat menu) { + var list = new ArrayList(); + list.add(GuiText.CompatibleUpgrades.text()); + + try { + IUpgradeInventory upgrades = menu.getCompatUpgrades(); + if (upgrades != null) { + list.addAll(appeng.api.upgrades.Upgrades.getTooltipLinesForMachine(upgrades.getUpgradableItem())); + } + } catch (Exception e) { + ExtendedAELogger.LOGGER.error("获取兼容升级列表时出错", e); + } + + return list; + } + + /** + * 兼容性升级菜单接口 + */ + public interface IUpgradeableMenuCompat { + ToolboxMenu getCompatToolbox(); + void setCompatToolbox(ToolboxMenu toolbox); + IUpgradeInventory getCompatUpgrades(); + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/MixinConditions.java b/src/main/java/com/extendedae_plus/mixin/MixinConditions.java new file mode 100644 index 0000000..ed45cbe --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/MixinConditions.java @@ -0,0 +1,79 @@ +package com.extendedae_plus.mixin; + +import net.minecraftforge.fml.ModList; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; + +import java.util.List; +import java.util.Set; + +/** + * Mixin条件加载插件 + * 用于根据模组存在情况动态加载不同的Mixin + */ +public class MixinConditions implements IMixinConfigPlugin { + + @Override + public void onLoad(String mixinPackage) { + // 初始化时调用 + } + + @Override + public String getRefMapperConfig() { + return null; + } + + @Override + public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { + // 对于升级相关的Mixin,检查appflux是否存在 + if (mixinClassName.contains("PatternProviderMenuUpgradesMixin") || + mixinClassName.contains("PatternProviderScreenUpgradesMixin") || + mixinClassName.contains("PatternProviderLogicUpgradesMixin") || + mixinClassName.contains("PatternProviderLogicHostUpgradesMixin")) { + + try { + // 检查ModList是否已初始化 + if (net.minecraftforge.fml.ModList.get() == null) { + System.out.println("[ExtendedAE_Plus] ModList未初始化,默认应用升级Mixin: " + mixinClassName); + return true; // 修改策略:未初始化时默认应用,运行时再检查 + } + + boolean appfluxExists = net.minecraftforge.fml.ModList.get().isLoaded("appflux"); + boolean shouldApply = !appfluxExists; + + System.out.println("[ExtendedAE_Plus] 升级Mixin检查: " + mixinClassName + + ", appflux存在: " + appfluxExists + + ", 应用Mixin: " + shouldApply); + + return shouldApply; + } catch (Exception e) { + System.out.println("[ExtendedAE_Plus] ModList检查失败,默认应用升级Mixin: " + mixinClassName); + return true; // 修改策略:出错时默认应用,运行时再检查 + } + } + + // 其他Mixin正常应用 + System.out.println("[ExtendedAE_Plus] 加载Mixin: " + mixinClassName); + return true; + } + + @Override + public void acceptTargets(Set myTargets, Set otherTargets) { + // 接受目标类 + } + + @Override + public List getMixins() { + return null; + } + + @Override + public void preApply(String targetClassName, org.objectweb.asm.tree.ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + // 应用前调用 + } + + @Override + public void postApply(String targetClassName, org.objectweb.asm.tree.ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + // 应用后调用 + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java index 9e84b7f..d02b4f9 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/client/gui/PatternProviderScreenUpgradesMixin.java @@ -10,6 +10,7 @@ import appeng.core.localization.GuiText; import appeng.menu.SlotSemantics; import appeng.menu.implementations.PatternProviderMenu; import com.extendedae_plus.bridge.IUpgradableMenu; +import com.extendedae_plus.compat.UpgradeSlotCompat; import net.minecraft.network.chat.Component; import net.minecraft.world.entity.player.Inventory; import org.spongepowered.asm.mixin.Mixin; @@ -21,15 +22,20 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.ArrayList; import java.util.List; -@Mixin(value = PatternProviderScreen.class, remap = false) +@Mixin(value = PatternProviderScreen.class, priority = 2000, remap = false) public abstract class PatternProviderScreenUpgradesMixin extends AEBaseScreen { @Inject(method = "", at = @At("TAIL")) private void eap$initUpgrades(PatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { + // 只有在应该启用升级卡槽时才添加升级面板 + if (!UpgradeSlotCompat.shouldAddUpgradePanelToScreen()) { + return; + } + this.widgets.add("upgrades", new UpgradesPanel( menu.getSlots(SlotSemantics.UPGRADE), this::eap$getCompatibleUpgrades)); - if (((IUpgradableMenu) menu).getToolbox().isPresent()) { + if (((IUpgradableMenu) menu).getToolbox() != null && ((IUpgradableMenu) menu).getToolbox().isPresent()) { this.widgets.add("toolbox", new ToolboxPanel(style, ((IUpgradableMenu) menu).getToolbox().getName())); } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderCompatMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderCompatMixin.java new file mode 100644 index 0000000..edba48a --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderCompatMixin.java @@ -0,0 +1,69 @@ +package com.extendedae_plus.mixin.ae2.compat; + +import appeng.api.upgrades.IUpgradeInventory; +import appeng.helpers.patternprovider.PatternProviderLogicHost; +import appeng.menu.AEBaseMenu; +import appeng.menu.ToolboxMenu; +import appeng.menu.implementations.PatternProviderMenu; +import com.extendedae_plus.compat.UpgradeSlotCompat; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.inventory.MenuType; +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; + +/** + * PatternProviderMenu的兼容性Mixin + * 优先级设置为500,低于appflux的默认优先级,避免冲突 + */ +@Mixin(value = PatternProviderMenu.class, priority = 500, remap = false) +public abstract class PatternProviderCompatMixin extends AEBaseMenu implements UpgradeSlotCompat.IUpgradeableMenuCompat { + + @Unique + private ToolboxMenu eap$compatToolbox; + + @Unique + private IUpgradeInventory eap$compatUpgrades; + + @Inject(method = "(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", + at = @At("TAIL")) + private void eap$initCompatUpgrades(MenuType menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) { + try { + // 检测是否应该启用升级卡槽功能 + if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + // 直接初始化升级功能 + this.eap$compatToolbox = new ToolboxMenu(this); + + if (host instanceof appeng.api.upgrades.IUpgradeableObject upgradeableHost) { + this.eap$compatUpgrades = upgradeableHost.getUpgrades(); + this.setupUpgrades(this.eap$compatUpgrades); + } + } + } catch (Exception e) { + // 静默处理异常,确保不会因为升级功能导致崩溃 + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("PatternProviderMenu兼容性升级初始化失败", e); + } + } + + @Override + public ToolboxMenu getCompatToolbox() { + return this.eap$compatToolbox; + } + + @Override + public void setCompatToolbox(ToolboxMenu toolbox) { + this.eap$compatToolbox = toolbox; + } + + @Override + public IUpgradeInventory getCompatUpgrades() { + return this.eap$compatUpgrades; + } + + // 构造函数,Mixin要求 + public PatternProviderCompatMixin(MenuType menuType, int id, Inventory playerInventory, Object host) { + super(menuType, id, playerInventory, host); + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicCompatMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicCompatMixin.java new file mode 100644 index 0000000..0945b85 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicCompatMixin.java @@ -0,0 +1,337 @@ +package com.extendedae_plus.mixin.ae2.compat; + +import appeng.api.networking.IManagedGridNode; +import appeng.api.networking.security.IActionSource; +import appeng.api.upgrades.IUpgradeInventory; +import appeng.api.upgrades.IUpgradeableObject; +import appeng.api.upgrades.UpgradeInventories; +import appeng.helpers.patternprovider.PatternProviderLogic; +import appeng.helpers.patternprovider.PatternProviderLogicHost; +import com.extendedae_plus.ae.items.ChannelCardItem; +import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; +import com.extendedae_plus.compat.UpgradeSlotCompat; +import com.extendedae_plus.init.ModItems; +import com.extendedae_plus.wireless.WirelessSlaveLink; +import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +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; + +import java.util.List; + +/** + * PatternProviderLogic的兼容性Mixin + * 优先级设置为500,避免与appflux冲突 + */ +@Mixin(value = PatternProviderLogic.class, priority = 500, remap = false) +public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObject, InterfaceWirelessLinkBridge { + + @Unique + private IUpgradeInventory eap$compatUpgrades = UpgradeInventories.empty(); + + @Unique + private WirelessSlaveLink eap$compatLink; + + @Unique + private long eap$compatLastChannel = -1; + + @Unique + private boolean eap$compatClientConnected = false; + + @Unique + private boolean eap$compatHasInitialized = false; + + @Unique + private int eap$compatDelayedInitTicks = 0; + + @Final + @Shadow + private PatternProviderLogicHost host; + + @Final + @Shadow + private IManagedGridNode mainNode; + + @Final + @Shadow + private IActionSource actionSource; + + @Unique + private void eap$compatOnUpgradesChanged() { + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return; + } + + try { + this.host.saveChanges(); + // 升级变更,重置并尝试初始化 + eap$compatLastChannel = -1; + eap$compatHasInitialized = false; + eap$compatInitializeChannelLink(); + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级变更处理失败", e); + } + } + + @Inject(method = "(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/patternprovider/PatternProviderLogicHost;I)V", + at = @At("TAIL")) + private void eap$compatInitUpgrades(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) { + try { + if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + this.eap$compatUpgrades = UpgradeInventories.forMachine( + host.getTerminalIcon().getItem(), + 1, + this::eap$compatOnUpgradesChanged + ); + } + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级初始化失败", e); + } + } + + @Inject(method = "writeToNBT", at = @At("TAIL")) + private void eap$compatSaveUpgrades(CompoundTag tag, CallbackInfo ci) { + try { + if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + this.eap$compatUpgrades.writeToNBT(tag, "compat_upgrades"); + } + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级保存失败", e); + } + } + + @Inject(method = "readFromNBT", at = @At("TAIL")) + private void eap$compatLoadUpgrades(CompoundTag tag, CallbackInfo ci) { + try { + if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + this.eap$compatUpgrades.readFromNBT(tag, "compat_upgrades"); + // 从 NBT 加载后,重置并尝试初始化 + eap$compatLastChannel = -1; + eap$compatHasInitialized = false; + eap$compatInitializeChannelLink(); + } + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级加载失败", e); + } + } + + @Inject(method = "addDrops", at = @At("TAIL")) + private void eap$compatDropUpgrades(List drops, CallbackInfo ci) { + try { + if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + for (var stack : this.eap$compatUpgrades) { + if (!stack.isEmpty()) { + drops.add(stack); + } + } + } + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级掉落失败", e); + } + } + + @Inject(method = "clearContent", at = @At("TAIL")) + private void eap$compatClearUpgrades(CallbackInfo ci) { + try { + if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + this.eap$compatUpgrades.clear(); + } + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级清理失败", e); + } + } + + @Override + public IUpgradeInventory getUpgrades() { + if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return this.eap$compatUpgrades; + } + return UpgradeInventories.empty(); + } + + @Override + public void eap$updateWirelessLink() { + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return; + } + + try { + if (eap$compatLink != null) { + eap$compatLink.updateStatus(); + } + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性无线链接更新失败", e); + } + } + + @Unique + public void eap$compatInitializeChannelLink() { + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return; + } + + try { + // 客户端早退 + if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) { + return; + } + + // 避免重复初始化 + if (eap$compatHasInitialized) { + return; + } + + // 等待网格完成引导 + if (!mainNode.hasGridBooted()) { + eap$compatDelayedInitTicks = Math.max(eap$compatDelayedInitTicks, 5); + try { + mainNode.ifPresent((grid, node) -> { + try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {} + }); + } catch (Throwable ignored) {} + return; + } + + long channel = 0L; + boolean found = false; + for (ItemStack stack : this.eap$compatUpgrades) { + if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { + channel = ChannelCardItem.getChannel(stack); + found = true; + break; + } + } + + if (!found) { + // 无频道卡:断开并视为初始化完成 + if (eap$compatLink != null) { + eap$compatLink.setFrequency(0L); + eap$compatLink.updateStatus(); + } + eap$compatHasInitialized = true; + return; + } + + if (eap$compatLink == null) { + var endpoint = new GenericNodeEndpointImpl(() -> host.getBlockEntity(), () -> this.mainNode.getNode()); + eap$compatLink = new WirelessSlaveLink(endpoint); + } + + eap$compatLink.setFrequency(channel); + eap$compatLink.updateStatus(); + + if (eap$compatLink.isConnected()) { + eap$compatHasInitialized = true; + } else { + eap$compatHasInitialized = false; + eap$compatDelayedInitTicks = Math.max(eap$compatDelayedInitTicks, 5); + try { + mainNode.ifPresent((grid, node) -> { + try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {} + }); + } catch (Throwable ignored) {} + } + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性频道链接初始化失败", e); + } + } + + @Override + public void eap$setClientWirelessState(boolean connected) { + if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + eap$compatClientConnected = connected; + } + } + + @Override + public boolean eap$isWirelessConnected() { + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return false; + } + + try { + if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) { + return eap$compatClientConnected; + } else { + return eap$compatLink != null && eap$compatLink.isConnected(); + } + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("检查兼容性无线连接状态失败", e); + return false; + } + } + + @Override + public boolean eap$hasTickInitialized() { + if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return eap$compatHasInitialized; + } + return true; + } + + @Override + public void eap$setTickInitialized(boolean initialized) { + if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + eap$compatHasInitialized = initialized; + } + } + + @Override + public void eap$handleDelayedInit() { + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return; + } + + try { + // 仅服务端 + if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) { + return; + } + if (!eap$compatHasInitialized) { + if (!mainNode.hasGridBooted()) { + if (eap$compatDelayedInitTicks > 0) { + eap$compatDelayedInitTicks--; + } + if (eap$compatDelayedInitTicks == 0) { + eap$compatDelayedInitTicks = 5; + try { + mainNode.ifPresent((grid, node) -> { + try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {} + }); + } catch (Throwable ignored) {} + } + } else { + eap$compatInitializeChannelLink(); + } + } + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性延迟初始化失败", e); + } + } + + @Inject(method = "onMainNodeStateChanged", at = @At("TAIL")) + private void eap$compatOnMainNodeStateChangedTail(CallbackInfo ci) { + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return; + } + + try { + eap$compatLastChannel = -1; + eap$compatHasInitialized = false; + eap$compatDelayedInitTicks = 10; + try { + mainNode.ifPresent((grid, node) -> { + try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {} + }); + } catch (Throwable ignored) {} + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性主节点状态变更处理失败", e); + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicHostCompatMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicHostCompatMixin.java new file mode 100644 index 0000000..00b5c01 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicHostCompatMixin.java @@ -0,0 +1,27 @@ +package com.extendedae_plus.mixin.ae2.compat; + +import appeng.api.upgrades.IUpgradeInventory; +import appeng.api.upgrades.IUpgradeableObject; +import appeng.api.upgrades.UpgradeInventories; +import appeng.helpers.patternprovider.PatternProviderLogic; +import appeng.helpers.patternprovider.PatternProviderLogicHost; +import com.extendedae_plus.compat.UpgradeSlotCompat; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +/** + * PatternProviderLogicHost的兼容性Mixin + * 优先级设置为500,避免与appflux冲突 + */ +@Mixin(value = PatternProviderLogicHost.class, priority = 500, remap = false) +public interface PatternProviderLogicHostCompatMixin extends IUpgradeableObject { + @Shadow PatternProviderLogic getLogic(); + + @Override + default IUpgradeInventory getUpgrades() { + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return UpgradeInventories.empty(); + } + return ((IUpgradeableObject) this.getLogic()).getUpgrades(); + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderScreenCompatMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderScreenCompatMixin.java new file mode 100644 index 0000000..eaee82c --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderScreenCompatMixin.java @@ -0,0 +1,88 @@ +package com.extendedae_plus.mixin.ae2.compat; + +import appeng.api.upgrades.Upgrades; +import appeng.client.gui.AEBaseScreen; +import appeng.client.gui.implementations.PatternProviderScreen; +import appeng.client.gui.style.ScreenStyle; +import appeng.client.gui.widgets.ToolboxPanel; +import appeng.client.gui.widgets.UpgradesPanel; +import appeng.core.localization.GuiText; +import appeng.menu.SlotSemantics; +import appeng.menu.implementations.PatternProviderMenu; +import com.extendedae_plus.compat.UpgradeSlotCompat; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; +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; + +import java.util.ArrayList; +import java.util.List; + +/** + * PatternProviderScreen的兼容性Mixin + * 优先级设置为500,避免与appflux冲突 + */ +@Mixin(value = PatternProviderScreen.class, priority = 500, remap = false) +public abstract class PatternProviderScreenCompatMixin extends AEBaseScreen { + + @Inject(method = "", at = @At("TAIL")) + private void eap$initCompatUpgrades(PatternProviderMenu menu, Inventory playerInventory, Component title, ScreenStyle style, CallbackInfo ci) { + try { + // 检测是否应该添加升级面板 + if (UpgradeSlotCompat.shouldAddUpgradePanelToScreen()) { + // 直接添加升级面板,不使用复杂的反射 + this.eap$addUpgradePanelDirect(menu, style); + } + } catch (Exception e) { + // 静默处理异常,确保不会因为升级功能导致崩溃 + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("PatternProviderScreen兼容性升级面板初始化失败", e); + } + } + + @Unique + private void eap$addUpgradePanelDirect(PatternProviderMenu menu, ScreenStyle style) { + try { + // 直接添加升级面板 + this.widgets.add("upgrades", new UpgradesPanel( + menu.getSlots(SlotSemantics.UPGRADE), + this::eap$getCompatibleUpgrades)); + + // 添加工具箱面板(如果菜单实现了兼容接口) + if (menu instanceof UpgradeSlotCompat.IUpgradeableMenuCompat compatMenu) { + var toolbox = compatMenu.getCompatToolbox(); + if (toolbox != null && toolbox.isPresent()) { + this.widgets.add("toolbox", new ToolboxPanel(style, toolbox.getName())); + } + } + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("直接添加升级面板失败", e); + } + } + + @Unique + private List eap$getCompatibleUpgrades() { + var list = new ArrayList(); + list.add(GuiText.CompatibleUpgrades.text()); + + try { + if (menu instanceof UpgradeSlotCompat.IUpgradeableMenuCompat compatMenu) { + var upgrades = compatMenu.getCompatUpgrades(); + if (upgrades != null) { + list.addAll(Upgrades.getTooltipLinesForMachine(upgrades.getUpgradableItem())); + } + } + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("获取兼容升级列表失败", e); + } + + return list; + } + + // 构造函数,Mixin要求 + public PatternProviderScreenCompatMixin(C menu, Inventory playerInventory, Component title, ScreenStyle style) { + super(menu, playerInventory, title, style); + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicHostUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicHostUpgradesMixin.java index 9254e5d..47f27dc 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicHostUpgradesMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicHostUpgradesMixin.java @@ -10,12 +10,15 @@ import org.spongepowered.asm.mixin.Shadow; /** * 让 PatternProviderLogicHost 作为 IUpgradeableObject 的代理,菜单可从 host 获取升级槽。 */ -@Mixin(value = PatternProviderLogicHost.class, remap = false) +@Mixin(value = PatternProviderLogicHost.class, priority = 2000, remap = false) public interface PatternProviderLogicHostUpgradesMixin extends IUpgradeableObject { @Shadow PatternProviderLogic getLogic(); @Override default IUpgradeInventory getUpgrades() { + if (!com.extendedae_plus.compat.UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return appeng.api.upgrades.UpgradeInventories.empty(); + } return ((IUpgradeableObject) this.getLogic()).getUpgrades(); } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java index 1a802cd..59f06c9 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java @@ -8,6 +8,7 @@ import appeng.api.upgrades.UpgradeInventories; import appeng.helpers.patternprovider.PatternProviderLogic; import appeng.helpers.patternprovider.PatternProviderLogicHost; import com.extendedae_plus.ae.items.ChannelCardItem; +import com.extendedae_plus.compat.UpgradeSlotCompat; import com.extendedae_plus.init.ModItems; import com.extendedae_plus.wireless.WirelessSlaveLink; import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl; @@ -29,7 +30,7 @@ import java.util.List; * 为 PatternProviderLogic 注入升级槽,实现 IUpgradeableObject。 * 仅负责升级槽的持久化/掉落/清空与初始化,不改变原有逻辑。 */ -@Mixin(value = PatternProviderLogic.class, remap = false) +@Mixin(value = PatternProviderLogic.class, priority = 2000, remap = false) public abstract class PatternProviderLogicUpgradesMixin implements IUpgradeableObject, InterfaceWirelessLinkBridge { @Unique private IUpgradeInventory eap$upgrades = UpgradeInventories.empty(); @@ -75,16 +76,27 @@ public abstract class PatternProviderLogicUpgradesMixin implements IUpgradeableO @Inject(method = "(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/patternprovider/PatternProviderLogicHost;I)V", at = @At("TAIL")) private void eap$initUpgrades(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) { + // 只有在应该启用升级卡槽时才初始化 + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return; + } + this.eap$upgrades = UpgradeInventories.forMachine(host.getTerminalIcon().getItem(), 1, this::eap$onUpgradesChanged); } @Inject(method = "writeToNBT", at = @At("TAIL")) private void eap$saveUpgrades(CompoundTag tag, CallbackInfo ci) { + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return; + } this.eap$upgrades.writeToNBT(tag, "upgrades"); } @Inject(method = "readFromNBT", at = @At("TAIL")) private void eap$loadUpgrades(CompoundTag tag, CallbackInfo ci) { + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return; + } this.eap$upgrades.readFromNBT(tag, "upgrades"); // 从 NBT 加载后,重置并尝试初始化(可能刚进入世界) eap$lastChannel = -1; @@ -94,6 +106,9 @@ public abstract class PatternProviderLogicUpgradesMixin implements IUpgradeableO @Inject(method = "addDrops", at = @At("TAIL")) private void eap$dropUpgrades(List drops, CallbackInfo ci) { + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return; + } for (var stack : this.eap$upgrades) { if (!stack.isEmpty()) { drops.add(stack); @@ -103,6 +118,9 @@ public abstract class PatternProviderLogicUpgradesMixin implements IUpgradeableO @Inject(method = "clearContent", at = @At("TAIL")) private void eap$clearUpgrades(CallbackInfo ci) { + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return; + } this.eap$upgrades.clear(); } @@ -115,6 +133,9 @@ public abstract class PatternProviderLogicUpgradesMixin implements IUpgradeableO @Override public IUpgradeInventory getUpgrades() { + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return UpgradeInventories.empty(); + } return this.eap$upgrades; } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java index 0cda3ae..c5ed6d7 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/menu/PatternProviderMenuUpgradesMixin.java @@ -8,6 +8,7 @@ import appeng.menu.AEBaseMenu; import appeng.menu.ToolboxMenu; import appeng.menu.implementations.PatternProviderMenu; import com.extendedae_plus.bridge.IUpgradableMenu; +import com.extendedae_plus.compat.UpgradeSlotCompat; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.inventory.MenuType; import org.spongepowered.asm.mixin.Final; @@ -18,7 +19,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -@Mixin(value = PatternProviderMenu.class, remap = false) +@Mixin(value = PatternProviderMenu.class, priority = 2000, remap = false) public abstract class PatternProviderMenuUpgradesMixin extends AEBaseMenu implements IUpgradableMenu { @Final @Shadow protected PatternProviderLogic logic; @@ -29,17 +30,28 @@ public abstract class PatternProviderMenuUpgradesMixin extends AEBaseMenu implem @Inject(method = "(Lnet/minecraft/world/inventory/MenuType;ILnet/minecraft/world/entity/player/Inventory;Lappeng/helpers/patternprovider/PatternProviderLogicHost;)V", at = @At("TAIL")) private void eap$initUpgrades(MenuType menuType, int id, Inventory playerInventory, PatternProviderLogicHost host, CallbackInfo ci) { + // 只有在应该启用升级卡槽时才初始化 + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return; + } + this.eap$toolbox = new ToolboxMenu(this); this.setupUpgrades(((IUpgradeableObject) host).getUpgrades()); } @Override public ToolboxMenu getToolbox() { + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return null; + } return this.eap$toolbox; } @Override public IUpgradeInventory getUpgrades() { + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + return appeng.api.upgrades.UpgradeInventories.empty(); + } return ((IUpgradeableObject) this.logic).getUpgrades(); } diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index ab3d360..5c81819 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -20,7 +20,7 @@ "ae2.client.gui.PatternEncodingTermScreenMixin", "ae2.client.gui.PatternProviderCloseMixin", "ae2.client.gui.PatternProviderScreenMixin", - "ae2.client.gui.PatternProviderScreenUpgradesMixin", + "ae2.compat.PatternProviderScreenCompatMixin", "ae2.client.gui.SlotGridLayoutMixin", "ae2.menu.CraftConfirmMenuGoBackMixin", "extendedae.accessor.GuiExPatternTerminalAccessor", @@ -63,9 +63,9 @@ "ae2.menu.PatternEncodingTermMenuMixin", "ae2.menu.PatternProviderMenuAdvancedMixin", "ae2.menu.PatternProviderMenuDoublingMixin", - "ae2.helpers.patternprovider.PatternProviderLogicUpgradesMixin", - "ae2.helpers.patternprovider.PatternProviderLogicHostUpgradesMixin", - "ae2.menu.PatternProviderMenuUpgradesMixin", + "ae2.compat.PatternProviderLogicCompatMixin", + "ae2.compat.PatternProviderLogicHostCompatMixin", + "ae2.compat.PatternProviderCompatMixin", "ae2.helpers.patternprovider.PatternProviderLogicTickerMixin", "ae2.parts.AEBasePartClientSyncMixin", "ae2.parts.automation.IOBusPartChannelCardMixin", @@ -84,5 +84,6 @@ "injectors": { "defaultRequire": 1 }, + "plugin": "com.extendedae_plus.mixin.MixinConditions", "priority": 1000 } From b8bfd1a3f663586ff46d88980f09f36cdb9a61ef Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Tue, 23 Sep 2025 18:10:27 +0800 Subject: [PATCH 23/34] =?UTF-8?q?=E5=85=BC=E5=AE=B9applied=20flux?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../compat/UpgradeSlotCompat.java | 9 ++ .../PatternProviderLogicCompatMixin.java | 125 +++++++++++++----- .../PatternProviderLogicHostCompatMixin.java | 2 +- 3 files changed, 102 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java b/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java index 6c39295..cef62a5 100644 --- a/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java +++ b/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java @@ -44,6 +44,15 @@ public class UpgradeSlotCompat { } } + /** + * 检测是否应该启用频道卡功能 + * 频道卡是我们独有的功能,即使appflux存在也应该启用 + * @return 总是返回true,因为频道卡功能不与appflux冲突 + */ + public static boolean shouldEnableChannelCard() { + return true; // 频道卡功能总是启用,因为appflux没有实现这个功能 + } + /** * 检测是否应该在Screen中添加升级面板 * @return true如果应该添加,false如果检测到appflux模组存在 diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicCompatMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicCompatMixin.java index 0945b85..1b922e2 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicCompatMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicCompatMixin.java @@ -27,7 +27,8 @@ import java.util.List; /** * PatternProviderLogic的兼容性Mixin - * 优先级设置为500,避免与appflux冲突 + * 优先级设置为1500,在appflux之后应用 + * 根据appflux是否存在来决定是否实现IUpgradeableObject接口 */ @Mixin(value = PatternProviderLogic.class, priority = 500, remap = false) public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObject, InterfaceWirelessLinkBridge { @@ -64,31 +65,60 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj @Unique private void eap$compatOnUpgradesChanged() { - if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { - return; - } - try { this.host.saveChanges(); - // 升级变更,重置并尝试初始化 - eap$compatLastChannel = -1; - eap$compatHasInitialized = false; - eap$compatInitializeChannelLink(); + // 频道卡功能独立于升级槽功能,总是处理 + if (UpgradeSlotCompat.shouldEnableChannelCard()) { + // 升级变更,重置并尝试初始化频道卡 + eap$compatLastChannel = -1; + eap$compatHasInitialized = false; + eap$compatInitializeChannelLink(); + } } catch (Exception e) { com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级变更处理失败", e); } } + + // 监听appflux的升级变化 - 通过注入到appflux的af_$onUpgradesChanged方法 + @Inject(method = "af_$onUpgradesChanged", at = @At("TAIL"), remap = false, require = 0) + private void eap$onAppfluxUpgradesChanged(CallbackInfo ci) { + try { + if (UpgradeSlotCompat.shouldEnableChannelCard()) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("监听到appflux升级变化,处理频道卡"); + // 升级变更,重置并尝试初始化频道卡 + eap$compatLastChannel = -1; + eap$compatHasInitialized = false; + eap$compatInitializeChannelLink(); + } + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("监听appflux升级变化失败", e); + } + } @Inject(method = "(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/patternprovider/PatternProviderLogicHost;I)V", at = @At("TAIL")) private void eap$compatInitUpgrades(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) { try { - if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("兼容性PatternProviderLogic初始化被调用"); + + boolean upgradeSlots = UpgradeSlotCompat.shouldEnableUpgradeSlots(); + boolean channelCard = UpgradeSlotCompat.shouldEnableChannelCard(); + + com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("升级槽功能: {}, 频道卡功能: {}", upgradeSlots, channelCard); + + if (upgradeSlots) { + // 只有在升级槽功能启用时才创建升级槽 this.eap$compatUpgrades = UpgradeInventories.forMachine( host.getTerminalIcon().getItem(), 1, this::eap$compatOnUpgradesChanged ); + com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("创建了完整的升级槽"); + } else if (channelCard) { + // 如果装了appflux,我们不创建自己的升级槽,而是监听appflux的升级槽 + com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("装了appflux,将监听其升级槽来处理频道卡"); + } else { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("跳过升级槽创建"); } } catch (Exception e) { com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级初始化失败", e); @@ -98,7 +128,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj @Inject(method = "writeToNBT", at = @At("TAIL")) private void eap$compatSaveUpgrades(CompoundTag tag, CallbackInfo ci) { try { - if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + if (UpgradeSlotCompat.shouldEnableUpgradeSlots() || UpgradeSlotCompat.shouldEnableChannelCard()) { this.eap$compatUpgrades.writeToNBT(tag, "compat_upgrades"); } } catch (Exception e) { @@ -109,12 +139,14 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj @Inject(method = "readFromNBT", at = @At("TAIL")) private void eap$compatLoadUpgrades(CompoundTag tag, CallbackInfo ci) { try { - if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + if (UpgradeSlotCompat.shouldEnableUpgradeSlots() || UpgradeSlotCompat.shouldEnableChannelCard()) { this.eap$compatUpgrades.readFromNBT(tag, "compat_upgrades"); - // 从 NBT 加载后,重置并尝试初始化 - eap$compatLastChannel = -1; - eap$compatHasInitialized = false; - eap$compatInitializeChannelLink(); + // 从 NBT 加载后,重置并尝试初始化频道卡 + if (UpgradeSlotCompat.shouldEnableChannelCard()) { + eap$compatLastChannel = -1; + eap$compatHasInitialized = false; + eap$compatInitializeChannelLink(); + } } } catch (Exception e) { com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("兼容性升级加载失败", e); @@ -124,7 +156,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj @Inject(method = "addDrops", at = @At("TAIL")) private void eap$compatDropUpgrades(List drops, CallbackInfo ci) { try { - if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + if (UpgradeSlotCompat.shouldEnableUpgradeSlots() || UpgradeSlotCompat.shouldEnableChannelCard()) { for (var stack : this.eap$compatUpgrades) { if (!stack.isEmpty()) { drops.add(stack); @@ -139,7 +171,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj @Inject(method = "clearContent", at = @At("TAIL")) private void eap$compatClearUpgrades(CallbackInfo ci) { try { - if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + if (UpgradeSlotCompat.shouldEnableUpgradeSlots() || UpgradeSlotCompat.shouldEnableChannelCard()) { this.eap$compatUpgrades.clear(); } } catch (Exception e) { @@ -150,14 +182,19 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj @Override public IUpgradeInventory getUpgrades() { if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { - return this.eap$compatUpgrades; + // 不装appflux时,返回我们自己的升级槽 + return this.eap$compatUpgrades != null ? this.eap$compatUpgrades : UpgradeInventories.empty(); + } else { + // 装了appflux时,这个方法不应该被调用,因为appflux的Mixin会覆盖它 + // 但是为了安全起见,返回空的升级槽 + com.extendedae_plus.util.ExtendedAELogger.LOGGER.debug("装了appflux时getUpgrades被调用,这不应该发生"); + return UpgradeInventories.empty(); } - return UpgradeInventories.empty(); } @Override public void eap$updateWirelessLink() { - if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + if (!UpgradeSlotCompat.shouldEnableChannelCard()) { return; } @@ -172,7 +209,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj @Unique public void eap$compatInitializeChannelLink() { - if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + if (!UpgradeSlotCompat.shouldEnableChannelCard()) { return; } @@ -200,11 +237,33 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj long channel = 0L; boolean found = false; - for (ItemStack stack : this.eap$compatUpgrades) { - if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { - channel = ChannelCardItem.getChannel(stack); - found = true; - break; + + // 获取升级槽 - 如果装了appflux则从appflux获取,否则从我们自己的获取 + IUpgradeInventory upgrades = null; + if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + // 不装appflux时使用我们自己的升级槽 + upgrades = this.eap$compatUpgrades; + } else if (UpgradeSlotCompat.shouldEnableChannelCard()) { + // 装了appflux时,尝试从PatternProviderLogic获取升级槽 + try { + if (this instanceof IUpgradeableObject) { + IUpgradeableObject upgradeableThis = (IUpgradeableObject) this; + upgrades = upgradeableThis.getUpgrades(); + com.extendedae_plus.util.ExtendedAELogger.LOGGER.debug("从appflux获取到升级槽: {}", upgrades != null); + } + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("获取appflux升级槽失败", e); + } + } + + if (upgrades != null) { + for (ItemStack stack : upgrades) { + if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { + channel = ChannelCardItem.getChannel(stack); + found = true; + com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("找到频道卡,频道: {}", channel); + break; + } } } @@ -244,14 +303,14 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj @Override public void eap$setClientWirelessState(boolean connected) { - if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + if (UpgradeSlotCompat.shouldEnableChannelCard()) { eap$compatClientConnected = connected; } } @Override public boolean eap$isWirelessConnected() { - if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + if (!UpgradeSlotCompat.shouldEnableChannelCard()) { return false; } @@ -269,7 +328,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj @Override public boolean eap$hasTickInitialized() { - if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + if (UpgradeSlotCompat.shouldEnableChannelCard()) { return eap$compatHasInitialized; } return true; @@ -277,14 +336,14 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj @Override public void eap$setTickInitialized(boolean initialized) { - if (UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + if (UpgradeSlotCompat.shouldEnableChannelCard()) { eap$compatHasInitialized = initialized; } } @Override public void eap$handleDelayedInit() { - if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + if (!UpgradeSlotCompat.shouldEnableChannelCard()) { return; } @@ -317,7 +376,7 @@ public abstract class PatternProviderLogicCompatMixin implements IUpgradeableObj @Inject(method = "onMainNodeStateChanged", at = @At("TAIL")) private void eap$compatOnMainNodeStateChangedTail(CallbackInfo ci) { - if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + if (!UpgradeSlotCompat.shouldEnableChannelCard()) { return; } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicHostCompatMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicHostCompatMixin.java index 00b5c01..f6cdee4 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicHostCompatMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/compat/PatternProviderLogicHostCompatMixin.java @@ -19,7 +19,7 @@ public interface PatternProviderLogicHostCompatMixin extends IUpgradeableObject @Override default IUpgradeInventory getUpgrades() { - if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots() && !UpgradeSlotCompat.shouldEnableChannelCard()) { return UpgradeInventories.empty(); } return ((IUpgradeableObject) this.getLogic()).getUpgrades(); From 7a8b8ccd6588b17d342e2fcba11efff648417331 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Tue, 23 Sep 2025 18:13:01 +0800 Subject: [PATCH 24/34] =?UTF-8?q?fix:=20=E6=9C=80=E7=BB=88=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E7=89=88=E6=97=A0=E9=99=90=E5=AD=98=E5=82=A8=E7=A3=81?= =?UTF-8?q?=E7=9B=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- gradle.properties | 2 +- .../com/extendedae_plus/ExtendedAEPlus.java | 24 +- .../InfinityBigIntegerCellHandler.java | 15 - .../InfinityBigIntegerCellInventory.java | 556 +++++++----------- .../ae/items/InfinityBigIntegerCellItem.java | 74 +-- .../command/InfinityDiskGiveCommand.java | 5 +- .../util/storage/InfinityConstants.java | 30 + .../util/storage/InfinityDataStorage.java | 57 +- .../util/storage/InfinityStorageManager.java | 269 +++------ 10 files changed, 419 insertions(+), 615 deletions(-) create mode 100644 src/main/java/com/extendedae_plus/util/storage/InfinityConstants.java diff --git a/README.md b/README.md index ca05466..af20501 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,6 @@ ExtendedAE Plus 是一个面向 Applied Energistics 2 与 ExtendedAE 的功能 - Applied Energistics 2(AE2):MIT License - SpongePowered Mixin:MIT License - Configuration(by Toma):MIT License -- AE2Things(by ProjectET):MIT License +- AE2Things-Forge(by Technici4n):MIT License 请查阅各上游项目以获取完整与最新的许可证信息。第三方组件的许可证与版权归其各自作者所有。 diff --git a/gradle.properties b/gradle.properties index b86c90f..b06db34 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G loom.platform = forge # Mod properties -mod_version = 1.4.2-fix2 +mod_version = 1.4.2-fix3 maven_group = com.extendedae_plus archives_name = extendedae_plus diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index 3e02a54..00cd4c1 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -3,22 +3,20 @@ package com.extendedae_plus; import appeng.api.storage.StorageCells; import appeng.menu.locator.MenuLocators; import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler; -import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory; import com.extendedae_plus.client.ClientRegistrar; +import com.extendedae_plus.command.InfinityDiskGiveCommand; import com.extendedae_plus.config.ModConfig; import com.extendedae_plus.init.*; import com.extendedae_plus.menu.locator.CuriosItemLocator; import com.extendedae_plus.util.storage.InfinityStorageManager; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerLevel; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.ModelEvent; import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.event.level.LevelEvent; +import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.event.RegisterCommandsEvent; -import com.extendedae_plus.command.InfinityDiskGiveCommand; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; @@ -54,15 +52,11 @@ public class ExtendedAEPlus { // 注册到Forge事件总线 MinecraftForge.EVENT_BUS.register(this); - MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad); // 注册命令注册监听 MinecraftForge.EVENT_BUS.addListener(this::onRegisterCommands); // 注册通用配置 ModConfig.init(); - // 注册 InfinityBigIntegerCellInventory 的事件监听(tick flush 与停止时 flush) - MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick); - MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerStopping); -// ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC); + MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::worldTick); } /** @@ -121,10 +115,12 @@ public class ExtendedAEPlus { } } - // 在世界加载时注册/加载 SavedData - private static void onLevelLoad(LevelEvent.Load event) { - if (event.getLevel() instanceof ServerLevel serverLevel) { - InfinityStorageManager.getForLevel(serverLevel); + + public static InfinityStorageManager STORAGE_INSTANCE = new InfinityStorageManager(); + + public static void worldTick(TickEvent.LevelTickEvent event) { + if (event.phase == TickEvent.Phase.START && event.side.isServer()) { + STORAGE_INSTANCE = InfinityStorageManager.getInstance(event.level.getServer()); } } diff --git a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java index bc7c29c..0609b6c 100644 --- a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java +++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java @@ -5,30 +5,15 @@ import appeng.api.storage.cells.ISaveProvider; import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem; import net.minecraft.world.item.ItemStack; -/** - * InfinityBigIntegerCellHandler - * - * 该类实现 AE2 的 ICellHandler,用于: - * - 判定某个 ItemStack 是否为本 mod 的 Infinity 存储单元 - * - 在 AE2 请求访问或创建存储单元时,创建并返回对应的 StorageCell 实例 - */ public class InfinityBigIntegerCellHandler implements ICellHandler { - /** Handler 单例,供注册与调用使用 */ public static final InfinityBigIntegerCellHandler INSTANCE = new InfinityBigIntegerCellHandler(); - /** - * 判断给定的 ItemStack 是否为 InfinityBigIntegerCell - */ @Override public boolean isCell(ItemStack is) { return is.getItem() instanceof InfinityBigIntegerCellItem; } - /** - * 在 AE2 需要访问或创建存储单元时返回对应的 InfinityBigIntegerCellInventory(StorageCell 实现)。 - * 参数 container 为 AE2 提供的保存回调(ISaveProvider),当 cell 需要持久化时会调用它。 - */ @Override public InfinityBigIntegerCellInventory getCellInventory(ItemStack is, ISaveProvider container) { return InfinityBigIntegerCellInventory.createInventory(is, container); diff --git a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java index efbf16b..efcce92 100644 --- a/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java +++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java @@ -8,7 +8,10 @@ import appeng.api.stacks.KeyCounter; import appeng.api.storage.cells.CellState; import appeng.api.storage.cells.ISaveProvider; import appeng.api.storage.cells.StorageCell; +import appeng.core.AELog; +import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem; +import com.extendedae_plus.util.storage.InfinityConstants; import com.extendedae_plus.util.storage.InfinityDataStorage; import com.extendedae_plus.util.storage.InfinityStorageManager; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; @@ -16,77 +19,45 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.network.chat.Component; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerLevel; import net.minecraft.world.item.ItemStack; -import net.minecraftforge.event.TickEvent; -import net.minecraftforge.event.server.ServerStoppingEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; +import java.util.Objects; import java.util.UUID; -import java.util.concurrent.ConcurrentLinkedQueue; -import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; /** - * InfinityBigIntegerCellInventory - *

- * 本类实现 AE2 的 StorageCell,表示单个 Infinity 存储单元的运行时数据与行为。 - * 主要职责: - * - 在内存中维护条目映射 (AEKey -> BigInteger 数量) - * - 提供插入/提取/列举/持久化等操作的实现 - * - 通过 UUID 将 ItemStack 与世界级的 SavedData 关联以实现持久化 - *

- * 重要字段: - * - stack: 关联的 ItemStack,NB T 中保存 UUID 与缓存信息 - * - container: AE2 提供的保存回调 (ISaveProvider),用于合并与触发持久化 - * - storedMap: 延迟初始化的内存映射,减少未使用时内存占用 - * - totalStored: 缓存的总数量 (BigInteger),避免频繁全表扫描 - * - isPersisted: 标记内存状态是否已同步到持久层 + * This code is inspired by AE2Things[](https://github.com/Technici4n/AE2Things-Forge), licensed under the MIT License.

+ * Original copyright (c) Technici4n

*/ public class InfinityBigIntegerCellInventory implements StorageCell { - - // 待持久化队列(用于 debounce:在服务器 tick 中合并持久化) - private static final ConcurrentLinkedQueue PENDING_PERSIST = new ConcurrentLinkedQueue<>(); - // 数字格式化对象:改为方法局部以避免静态非线程安全问题 - - // 关联的 ItemStack(含可能的 uuid NBT) - private final ItemStack stack; + private final InfinityBigIntegerCellItem cell; + // 磁盘本身 + private final ItemStack self; // AE2 提供的保存提供者,用于在容器中批量保存时触发回调 private final ISaveProvider container; - // 内存中的键-数量映射(使用 BigInteger 支持超长数量,延迟初始化) - private Object2ObjectMap storedMap = null; + // 存储物品键和数量的映射 + private Object2ObjectMap AEKey2AmountsMap; + // 存储的物品种类数量 + private int totalAEKeyType; + // 存储的物品总数 + private BigInteger totalAEKey2Amounts = BigInteger.ZERO; // 标记是否已持久化到 SavedData private boolean isPersisted = true; - // 缓存的总存储量,避免每次调用进行全表扫描 - private BigInteger totalStored = BigInteger.ZERO; - /** - * 私有构造器:通过 createInventory 工厂方法调用 - * - * @param stack 关联的物品堆 - * @param saveProvider AE2 的保存回调(可为 null) - */ - private InfinityBigIntegerCellInventory(ItemStack stack, ISaveProvider saveProvider) { - this.stack = stack; - container = saveProvider; - // 不在构造时创建 storedMap,推迟到实际访问或首次写入时初始化 - this.storedMap = null; - } - // 创建存储单元库存实例的静态方法 - static InfinityBigIntegerCellInventory createInventory(ItemStack stack, ISaveProvider saveProvider) { - if (stack.getItem() instanceof InfinityBigIntegerCellItem) { - return new InfinityBigIntegerCellInventory(stack, saveProvider); - } - return null; - } - - // 获取全局存储实例 - private static InfinityStorageManager getStorageInstance() { - return InfinityStorageManager.INSTANCE; + public InfinityBigIntegerCellInventory(InfinityBigIntegerCellItem cell, ItemStack stack, ISaveProvider saveProvider) { + // 保存存储单元类型(InfinityBigIntegerCellItem 实例),用于访问磁盘属性 + this.cell = cell; + // 保存物品堆栈,表示磁盘本身,包含运行时的 NBT 数据 + this.self = stack; + // 保存提供者,用于触发数据保存 + this.container = saveProvider; + // 初始化 storedAmounts 为 null,延迟加载物品数据 + this.AEKey2AmountsMap = null; + // 初始化磁盘数据 + initData(); } // 将 BigInteger 格式化为带单位的字符串,保留两位小数 @@ -107,29 +78,43 @@ public class InfinityBigIntegerCellInventory implements StorageCell { return df.format(bd.doubleValue()) + units[idx]; } - // 获取当前存储单元的数据存储对象 + // 获取磁盘的 InfinityDataStorage 数据 private InfinityDataStorage getCellStorage() { - if (this.getUUID() == null) { - // 如果没有UUID,返回空存储(返回新实例以避免共享可变状态) - return InfinityDataStorage.empty(); + // 如果磁盘有 UUID,返回对应的 InfinityDataStorage + if (getUUID() != null) { + return getStorageManagerInstance().getOrCreateCell(getUUID()); } else { - // 在访问全局 SavedData 之前做防御性检查,避免在客户端或尚未初始化的情况下 NPE - InfinityStorageManager mgr = getStorageInstance(); - if (mgr == null) { - // SavedData 尚未就绪,视为空存储(返回新实例以避免共享可变状态) - return InfinityDataStorage.empty(); - } - // 否则获取或创建对应UUID的存储 - return mgr.getOrCreateCell(getUUID()); + // 否则返回空的 InfinityDataStorage + return InfinityDataStorage.EMPTY; } } - // 获取存储单元状态(空/非空) + // 初始化磁盘数据 + private void initData() { + // 如果磁盘有 UUID,加载存储的物品数据 + if (hasUUID()) { + this.totalAEKeyType = getCellStorage().amounts.size(); + this.totalAEKey2Amounts = getCellStorage().itemCount.equals(BigInteger.ZERO) ? + BigInteger.ZERO : + getCellStorage().itemCount; + + } else { + // 否则初始化为空 + this.totalAEKeyType = 0; + this.totalAEKey2Amounts = BigInteger.ZERO; + // 加载物品数据 + getCellStoredMap(); + } + } + + // 获取存储单元的状态(空、部分填充) @Override public CellState getStatus() { - if (this.getCellStoredMap().isEmpty()) { + // 如果没有存储任何物品,返回空状态 + if (this.getTotalAEKey2Amounts().equals(BigInteger.ZERO)) { return CellState.EMPTY; } + // 否则返回满状态 return CellState.NOT_EMPTY; } @@ -139,225 +124,226 @@ public class InfinityBigIntegerCellInventory implements StorageCell { return 512; } + // 持久化存储单元数据到全局存储 + @Override + public void persist() { + if (this.isPersisted) + return; + + if (totalAEKey2Amounts.equals(BigInteger.ZERO)) { + if (hasUUID()) { + getStorageManagerInstance().removeCell(getUUID()); + if (self.hasTag()) { + var tag = self.getTag(); + // remove persisted identifiers and cached summary fields from the ItemStack + tag.remove(InfinityConstants.INFINITY_CELL_UUID); + tag.remove(InfinityConstants.INFINITY_ITEM_TOTAL); + tag.remove(InfinityConstants.INFINITY_ITEM_TYPES); + // backward compat: also remove internal cell item count key if present + tag.remove(InfinityConstants.INFINITY_CELL_ITEM_COUNT); + } + initData(); + } + return; + } + + // 创建物品键列表 + ListTag keys = new ListTag(); + // 创建物品数量列表 + ListTag amounts = new ListTag(); + // 初始化物品总数 + BigInteger itemCount = BigInteger.ZERO; + + for (var entry : this.AEKey2AmountsMap.object2ObjectEntrySet()) { + BigInteger amount = entry.getValue(); + // 如果数量大于 0,添加到键和数量列表 + if (amount.compareTo(BigInteger.ZERO) > 0) { + keys.add(entry.getKey().toTagGeneric()); + CompoundTag amountTag = new CompoundTag(); + amountTag.putByteArray("value", amount.toByteArray()); + amounts.add(amountTag); + + itemCount = itemCount.add(amount); + } + } + + if (keys.isEmpty()) { + getStorageManagerInstance().updateCell(getUUID(), new InfinityDataStorage()); + } else { + getStorageManagerInstance().modifyDisk(getUUID(), keys, amounts, itemCount); + } + + // 更新存储的物品种类数量 + this.totalAEKeyType = this.AEKey2AmountsMap.size(); + // 更新存储的物品总数 + this.totalAEKey2Amounts = itemCount; + // 将物品总数与种类数量存入物品堆栈的 NBT(用于快捷查看/tooltip),同时保留旧字段以兼容历史版本 + var tag = self.getOrCreateTag(); + tag.putByteArray(InfinityConstants.INFINITY_ITEM_TOTAL, itemCount.toByteArray()); + tag.putInt(InfinityConstants.INFINITY_ITEM_TYPES, this.totalAEKeyType); + // backward compat storage field (kept for legacy readers) + tag.putByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT, itemCount.toByteArray()); + + // 标记数据已持久化 + this.isPersisted = true; + } + // 获取存储单元的描述(此处返回null,可自定义) @Override public Component getDescription() { return null; } + // 静态方法,创建存储单元库存 + public static InfinityBigIntegerCellInventory createInventory(ItemStack stack, ISaveProvider saveProvider) { + // 检查物品堆栈是否为空 + Objects.requireNonNull(stack, "Cannot create cell inventory for null itemstack"); + // 检查物品是否为 IDISKCellItem 类型 + if (!(stack.getItem() instanceof InfinityBigIntegerCellItem cell)) { + return null; + } + // 创建并返回新的 DISKCellInventory 实例 + return new InfinityBigIntegerCellInventory(cell, stack, saveProvider); + } + + // 获取存储的物品总数 + public BigInteger getTotalAEKey2Amounts() { + return this.totalAEKey2Amounts; + } + + // 获取存储的物品种类数量 + public int getTotalAEKeyType() { + return this.totalAEKeyType; + } + // 判断物品堆栈是否有UUID public boolean hasUUID() { - return stack.hasTag() && stack.getOrCreateTag().contains("uuid"); + return self.hasTag() && self.getOrCreateTag().contains(InfinityConstants.INFINITY_CELL_UUID); } // 获取物品堆栈的UUID public UUID getUUID() { - if (this.hasUUID()) - return stack.getOrCreateTag().getUUID("uuid"); - else + if (this.hasUUID()) { + return self.getOrCreateTag().getUUID(InfinityConstants.INFINITY_CELL_UUID); + } else { return null; + } } // 获取或初始化存储映射 private Object2ObjectMap getCellStoredMap() { - if (storedMap == null) { - storedMap = new Object2ObjectOpenHashMap<>(); + if (AEKey2AmountsMap == null) { + AEKey2AmountsMap = new Object2ObjectOpenHashMap<>(); this.loadCellStoredMap(); } - return storedMap; - } - - // 从存储中加载物品映射 - private void loadCellStoredMap() { - boolean corruptedTag = false; // 标记数据是否损坏 - if (!stack.hasTag()) return; - InfinityDataStorage storage = this.getCellStorage(); - ListTag keys = storage.keys; - ListTag amounts = storage.amounts; - int len = Math.min(keys.size(), amounts.size()); - for (int i = 0; i < len; i++) { - AEKey key = AEKey.fromTagGeneric(keys.getCompound(i)); - CompoundTag amtTag = amounts.getCompound(i); - try { - BigInteger amount; - if (amtTag.contains("l")) { - long v = amtTag.getLong("l"); - amount = BigInteger.valueOf(v); - } else if (amtTag.contains("s")) { - amount = new BigInteger(amtTag.getString("s")); - } else { - corruptedTag = true; - continue; - } - if (amount.compareTo(BigInteger.ZERO) <= 0 || key == null) { - corruptedTag = true; - } else { - // storedMap 已在 getCellStoredMap() 中初始化,直接使用字段以避免额外方法开销 - storedMap.put(key, amount); - // 更新缓存的总数 - totalStored = totalStored.add(amount); - } - } catch (NumberFormatException ex) { - corruptedTag = true; - } - } - // 如果有损坏,保存修正后的数据 - if (corruptedTag) { - this.saveChanges(); - } - } - - // 标记数据需要保存,并通知容器或直接持久化 - private void saveChanges() { - // 标记为未持久化,交由容器或延迟任务合并写入以减少 I/O - isPersisted = false; - if (container != null) { - // 当存在容器时,优先让容器统一处理持久化 - container.saveChanges(); - } else { - // 如果没有容器,入队等待服务器 tick 在主线程统一持久化,避免频繁 I/O - if (!PENDING_PERSIST.contains(this)) { - PENDING_PERSIST.offer(this); - } - } + return AEKey2AmountsMap; } // 获取所有可用的物品堆栈及其数量 @Override public void getAvailableStacks(KeyCounter out) { BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE); - Object2ObjectMap map = getCellStoredMap(); - for (Object2ObjectMap.Entry entry : map.object2ObjectEntrySet()) { + if(this.getCellStoredMap() == null) return; + for (var entry : this.getCellStoredMap().object2ObjectEntrySet()) { AEKey key = entry.getKey(); BigInteger value = entry.getValue(); - // 当前 KeyCounter 中已有的值(long) + // 获取 KeyCounter 中已有的值 long existing = out.get(key); - // 将 existing 与当前 value 做 BigInteger 累加并饱和到 Long.MAX_VALUE + // 计算总和并限制到 Long.MAX_VALUE BigInteger sum = BigInteger.valueOf(existing).add(value); long toSet = sum.compareTo(maxLong) > 0 ? Long.MAX_VALUE : sum.longValue(); - - // KeyCounter 没有 set(key,long) 的统一接口暴露(只有 add/remove),所以先移除已存在的值再设置。 - // 为避免读取-写入竞争,我们计算出要新增的 delta 并调用 add(key, delta) + // 更新 KeyCounter if (existing == Long.MAX_VALUE) { - // 已经饱和,无需再添加 continue; } - long delta; - if (toSet == Long.MAX_VALUE) { - delta = Long.MAX_VALUE - existing; - } else { - delta = toSet - existing; - } + long delta = toSet - existing; if (delta != 0) { out.add(key, delta); } } } - // 持久化存储单元数据到全局存储 - @Override - public void persist() { - if (this.isPersisted) - return; - // 若全局存储管理器尚未就绪,重新入队并在未来 tick 重试 - InfinityStorageManager mgr = getStorageInstance(); - if (mgr == null) { - try { - var id = this.getUUID(); - LOGGER.info("Persist deferred for cell {}: storage manager not ready", id); - } catch (Throwable ignored) { - } - if (!PENDING_PERSIST.contains(this)) { - PENDING_PERSIST.offer(this); - } - return; - } - Object2ObjectMap map = this.getCellStoredMap(); - if (map.isEmpty()) { - // 如果存储为空,移除UUID和全局存储中的数据 - if (this.hasUUID()) { - mgr.removeCell(getUUID()); - if (stack.getTag() != null) { - stack.getTag().remove("uuid"); - // 移除缓存的 total 字段 - stack.getTag().remove("total"); - } - // 标记为已持久化,避免重复尝试 - isPersisted = true; - } - return; + // 从存储中加载物品映射 + private void loadCellStoredMap() { + boolean dataCorruption = false; + if (!self.hasTag()) return; + + var keys = getCellStorage().keys; + var amounts = getCellStorage().amounts; + // 数据损坏 + if (keys.size() != amounts.size()) { + AELog.warn("Loading storage cell with mismatched amounts/tags: %d != %d", amounts.size(), keys.size()); } - // 构建要保存的Key和数量列表(混合表示:long 或 string) - ListTag amountTags = new ListTag(); - ListTag keys = new ListTag(); - for (Object2ObjectMap.Entry entry : map.object2ObjectEntrySet()) { - BigInteger amount = entry.getValue(); - if (amount.compareTo(BigInteger.ZERO) > 0) { - keys.add(entry.getKey().toTagGeneric()); - CompoundTag amt = new CompoundTag(); - if (amount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) { - amt.putLong("l", amount.longValue()); - } else { - amt.putString("s", amount.toString()); - } - amountTags.add(amt); - } - } - // 如果没有Key,更新为空存储,否则保存数据 - if (keys.isEmpty()) { - mgr.updateCell(this.getUUID(), new InfinityDataStorage()); - } else { - // amounts 现在为 CompoundTag 列表 - mgr.modifyCell(this.getUUID(), keys, amountTags); - } - // 将缓存的 totalStored 同步到 ItemStack 的 NBT,优先使用 long - if (stack.getOrCreateTag() != null) { - if (totalStored.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) { - stack.getOrCreateTag().putLong("total", totalStored.longValue()); + // 遍历数量和键,加载到 AEKey2AmountsMap + for (int i = 0; i < amounts.size(); i++) { + AEKey key = AEKey.fromTagGeneric(keys.getCompound(i)); + BigInteger amount = new BigInteger(amounts.getCompound(i).getByteArray("value")); + // 检查数据是否损坏 + if (amount.compareTo(BigInteger.ZERO) <= 0 || key == null) { + dataCorruption = true; } else { - stack.getOrCreateTag().putString("total", totalStored.toString()); + AEKey2AmountsMap.put(key, amount); } - // 将当前已存储的不同物品种类数缓存到 NBT(键名: "types"),用于客户端 tooltip 显示 - int typesCount = this.getCellStoredMap().size(); - stack.getOrCreateTag().putInt("types", typesCount); } - isPersisted = true; + if (dataCorruption) { + this.saveChanges(); + } + } + + // 获取全局存储实例 + private static InfinityStorageManager getStorageManagerInstance() { + return ExtendedAEPlus.STORAGE_INSTANCE; + } + + // 标记数据需要保存,并通知容器或直接持久化 + private void saveChanges() { + // 更新存储的物品种类数量 + this.totalAEKeyType = this.AEKey2AmountsMap.size(); + // 重置物品总数 + this.totalAEKey2Amounts = BigInteger.ZERO; + // 计算物品总数 + for (BigInteger AEKey2Amounts : this.AEKey2AmountsMap.values()) { + this.totalAEKey2Amounts = this.totalAEKey2Amounts.add(AEKey2Amounts); + } + // 标记数据未持久化 + this.isPersisted = false; + // 如果有保存提供者,通知保存 + if (this.container != null) { + this.container.saveChanges(); + } else { + // 否则立即持久化 + this.persist(); + } } // 插入物品到存储单元 @Override public long insert(AEKey what, long amount, Actionable mode, IActionSource source) { // 数量为0或类型不匹配直接返回 - if (amount == 0) + if (amount == 0){ return 0; + } // 不允许存储无限单元自身 - if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) + if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) { return 0; + } + // 如果没有UUID,尝试在服务器端且存储管理器已就绪时生成UUID并初始化存储 if (!this.hasUUID()) { - InfinityStorageManager mgr = getStorageInstance(); - // 仅在 SavedData 已就绪(通常在服务器端)时创建 UUID 并注册持久化存储 - if (mgr != null) { - stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID()); - mgr.getOrCreateCell(getUUID()); - // 确保 storedMap 初始化并从持久层加载数据 - this.getCellStoredMap(); - } else { - // SavedData 未就绪:把插入作为本地内存操作(不生成 UUID,不持久化),确保 storedMap 初始化以容纳临时数据 - this.getCellStoredMap(); - } + self.getOrCreateTag().putUUID(InfinityConstants.INFINITY_CELL_UUID, UUID.randomUUID()); + getStorageManagerInstance().getOrCreateCell(getUUID()); + loadCellStoredMap(); } - Object2ObjectMap map = this.getCellStoredMap(); - BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO); + // 获取当前物品数量 + BigInteger currentAmount = this.getCellStoredMap().getOrDefault(what, BigInteger.ZERO); + if (mode == Actionable.MODULATE) { // 实际插入,更新数量并保存 BigInteger newAmount = currentAmount.add(BigInteger.valueOf(amount)); - map.put(what, newAmount); - // 更新 cached total - totalStored = totalStored.add(BigInteger.valueOf(amount)); + getCellStoredMap().put(what, newAmount); this.saveChanges(); } return amount; @@ -366,35 +352,26 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 从存储单元提取物品 @Override public long extract(AEKey what, long amount, Actionable mode, IActionSource source) { - Object2ObjectMap map = this.getCellStoredMap(); - BigInteger currentAmount = map.getOrDefault(what, BigInteger.ZERO); + BigInteger currentAmount = this.getCellStoredMap().getOrDefault(what, BigInteger.ZERO); + // 如果有物品可提取 if (currentAmount.compareTo(BigInteger.ZERO) > 0) { + BigInteger requested = BigInteger.valueOf(amount); - if (currentAmount.compareTo(requested) <= 0) { - // 提取全部 - long ret; - if (currentAmount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) { - ret = Long.MAX_VALUE; - } else { - ret = currentAmount.longValue(); - } + + // 如果提取数量大于等于当前数量 + if (requested.compareTo(currentAmount) >= 0) { if (mode == Actionable.MODULATE) { - map.remove(what); - // 更新 cached total - // 如果 currentAmount 大于 Long.MAX_VALUE,totalStored 减去 currentAmount 会保留大整数 - totalStored = totalStored.subtract(currentAmount); + getCellStoredMap().remove(what); this.saveChanges(); } - return ret; + return currentAmount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0 ? Long.MAX_VALUE : currentAmount.longValue(); } else { - // 提取部分 + // 提取部分数量 if (mode == Actionable.MODULATE) { - map.put(what, currentAmount.subtract(requested)); - // 更新 cached total - totalStored = totalStored.subtract(requested); + getCellStoredMap().put(what, currentAmount.subtract(requested)); this.saveChanges(); } - return amount; + return requested.longValue(); } } return 0; @@ -403,89 +380,6 @@ public class InfinityBigIntegerCellInventory implements StorageCell { // 获取存储单元内所有物品的总数量(格式化字符串) public String getTotalStorage() { // 使用缓存的 totalStored,避免每次全表扫描 - return formatBigInteger(totalStored); - } - - /** 定时 tick 保存计数器 */ - private static int SAVE_TICK_COUNTER = 0; - - /** tick 间隔,单位为服务器 tick(20 tick ≈ 1 秒) */ - private static final int SAVE_TICK_INTERVAL = 20; // 约30秒 - - /** 服务器 tick 事件:处理队列并定时保存 */ - @SubscribeEvent - public static void onServerTick(TickEvent.ServerTickEvent event) { - if (event.phase != TickEvent.Phase.END) return; - - // 处理待持久化队列 - InfinityBigIntegerCellInventory inv; - while ((inv = PENDING_PERSIST.poll()) != null) { - try { - if (!inv.isPersisted) { - inv.persist(); - } - } catch (Throwable ex) { - LOGGER.info("InfinityBigIntegerCellInventory onServerTick error: {}", ex.getMessage()); - } - } - - // 定时标记全局存储为脏 - try { - if (++SAVE_TICK_COUNTER >= SAVE_TICK_INTERVAL) { - SAVE_TICK_COUNTER = 0; - if (InfinityStorageManager.INSTANCE != null) { - InfinityStorageManager.INSTANCE.setDirty(); - } - } - } catch (Throwable ex) { - LOGGER.info("InfinityBigIntegerCellInventory tick dirty set error: {}", ex.getMessage()); - } - } - - // 在服务器停止时被调用,立即强制持久化队列中的所有实例 - @SubscribeEvent - public static void onServerStopping(ServerStoppingEvent event) { - // 尝试在服务端停止流程开始时初始化 SavedData(确保 INSTANCE 可用以便后续持久化) - try { - MinecraftServer server = event.getServer(); - if (server != null) { - for (ServerLevel level : server.getAllLevels()) { - InfinityStorageManager.getForLevel(level); - } - } - } catch (Throwable ignored) { - LOGGER.info("InfinityBigIntegerCellInventory onServerStopping init error: {}", ignored.getMessage()); - } - - InfinityBigIntegerCellInventory inv; - while ((inv = PENDING_PERSIST.poll()) != null) { - try { - if (!inv.isPersisted) { - inv.persist(); - } - } catch (Throwable ex) { - LOGGER.info("InfinityBigIntegerCellInventory onServerStopping persist error: {}", ex.getMessage()); } - } - - // 强制写回磁盘,确保直接关闭游戏也能保存 - try { - InfinityStorageManager mgr = InfinityStorageManager.INSTANCE; - if (mgr != null) { - MinecraftServer server = event.getServer(); - if (server != null) { - for (ServerLevel level : server.getAllLevels()) { - try { - mgr.forceSaveAll(level); - LOGGER.info("Stop forceSaveAll for level {}", level.dimension().location()); - } catch (Throwable ex) { - LOGGER.info("InfinityBigIntegerCellInventory forceSaveAll error for level {}: {}", - level.dimension().location(), ex.getMessage()); - } - } - } - } - } catch (Throwable ex) { - LOGGER.info("InfinityBigIntegerCellInventory onServerStopping forceSaveAll error: {}", ex.getMessage()); - } + return formatBigInteger(totalAEKey2Amounts); } } diff --git a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java index 75baebf..2b4af58 100644 --- a/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java +++ b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java @@ -1,11 +1,12 @@ package com.extendedae_plus.ae.items; +import appeng.api.config.FuzzyMode; +import appeng.api.storage.cells.ICellWorkbenchItem; import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory; +import com.extendedae_plus.util.storage.InfinityConstants; import com.google.common.base.Preconditions; import net.minecraft.ChatFormatting; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.LongTag; -import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.Item; @@ -16,24 +17,15 @@ import net.minecraftforge.registries.ForgeRegistries; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; -import java.math.BigInteger; import java.util.List; import java.util.Objects; -public class InfinityBigIntegerCellItem extends Item { +public class InfinityBigIntegerCellItem extends Item implements ICellWorkbenchItem { public InfinityBigIntegerCellItem() { super(new Properties().stacksTo(1).fireResistant()); } - /** - * 在物品悬停提示中展示额外信息。 - * 功能: - * - 若 ItemStack 的 NBT 含有 UUID,则显示该 UUID(不会触发服务器加载或持久化行为) - * - 若 NBT 同步了 total 字段,则读取并格式化显示总存储量(使用 Inventory 的 formatBigInteger) - * - * 设计说明:客户端 tooltip 不主动访问服务端 SavedData,以避免不必要的 I/O 与状态变更。 - */ @Override public void appendHoverText(ItemStack stack, @Nullable Level world, @@ -45,15 +37,16 @@ public class InfinityBigIntegerCellItem extends Item { Preconditions.checkArgument(stack.getItem() == this); // 仅在 ItemStack 自身存在 UUID 时显示 UUID,避免触发持久化或加载逻辑 CompoundTag tag = stack.getTag(); - if (tag != null && tag.contains("uuid")) { - String uuidStr = tag.getUUID("uuid").toString(); + if (tag != null && tag.contains(InfinityConstants.INFINITY_CELL_UUID)) { + String uuidStr = tag.getUUID(InfinityConstants.INFINITY_CELL_UUID).toString(); tooltip.add( Component.literal("UUID: ").withStyle(ChatFormatting.GRAY).append(Component.literal(uuidStr).withStyle(ChatFormatting.YELLOW)) ); - // 读取并显示已缓存的种类数量(types),表示当前存储了多少种不同的 AEKey - if (tag.contains("types")) { + + // 显示已缓存的种类数量(types)——优先使用 ItemStack 缓存字段 + if (tag.contains(InfinityConstants.INFINITY_ITEM_TYPES)) { try { - int types = tag.getInt("types"); + int types = tag.getInt(InfinityConstants.INFINITY_ITEM_TYPES); tooltip.add( Component.literal("Types: ").withStyle(ChatFormatting.GRAY).append(Component.literal(String.valueOf(types)).withStyle(ChatFormatting.GREEN)) ); @@ -61,24 +54,30 @@ public class InfinityBigIntegerCellItem extends Item { // ignore malformed value } } - // 读取并显示已缓存的 total(支持 long 或 string),使用格式化函数展示友好单位 - if (tag.contains("total")) { - BigInteger total = BigInteger.ZERO; - Tag t = tag.get("total"); + + // 显示物品总数(formatted)。优先使用缓存的 INFINITY_ITEM_TOTAL 字段(byte[]),否则回退为 legacy 字段或不显示 + if (tag.contains(InfinityConstants.INFINITY_ITEM_TOTAL)) { try { - if (t instanceof LongTag) { - total = BigInteger.valueOf(tag.getLong("total")); - } else { - String s = tag.getString("total"); - total = new BigInteger(s); - } + byte[] bytes = tag.getByteArray(InfinityConstants.INFINITY_ITEM_TOTAL); + java.math.BigInteger total = new java.math.BigInteger(bytes); + String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total); + tooltip.add( + Component.literal("Total: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA)) + ); } catch (Exception ignored) { - // 解析失败保持为 0 + // ignore malformed value + } + } else if (tag.contains(InfinityConstants.INFINITY_CELL_ITEM_COUNT)) { + try { + byte[] bytes = tag.getByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT); + java.math.BigInteger total = new java.math.BigInteger(bytes); + String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total); + tooltip.add( + Component.literal("Total: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA)) + ); + } catch (Exception ignored) { + // ignore malformed value } - String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total); - tooltip.add( - Component.literal("Byte: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA)) - ); } } } @@ -90,7 +89,16 @@ public class InfinityBigIntegerCellItem extends Item { ItemStack stack = new ItemStack(Objects.requireNonNull( ForgeRegistries.ITEMS.getValue(new ResourceLocation("extendedae_plus", "infinity_biginteger_cell") ))); - stack.getOrCreateTag().putUUID("uuid", uuid); + stack.getOrCreateTag().putUUID(InfinityConstants.INFINITY_CELL_UUID, uuid); return stack; } + + @Override + public FuzzyMode getFuzzyMode(ItemStack itemStack) { + return null; + } + + @Override + public void setFuzzyMode(ItemStack itemStack, FuzzyMode fuzzyMode) { + } } \ No newline at end of file diff --git a/src/main/java/com/extendedae_plus/command/InfinityDiskGiveCommand.java b/src/main/java/com/extendedae_plus/command/InfinityDiskGiveCommand.java index 306e483..67305f3 100644 --- a/src/main/java/com/extendedae_plus/command/InfinityDiskGiveCommand.java +++ b/src/main/java/com/extendedae_plus/command/InfinityDiskGiveCommand.java @@ -1,5 +1,6 @@ package com.extendedae_plus.command; +import com.extendedae_plus.ExtendedAEPlus; import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem; import com.extendedae_plus.util.storage.InfinityStorageManager; import com.mojang.brigadier.CommandDispatcher; @@ -33,7 +34,7 @@ public class InfinityDiskGiveCommand { source.sendFailure(Component.literal("This command must be run on server side.")); return 0; } - InfinityStorageManager mgr = InfinityStorageManager.getForLevel((ServerLevel) player.level()); + InfinityStorageManager mgr = ExtendedAEPlus.STORAGE_INSTANCE; if (mgr == null) { source.sendFailure(Component.literal("InfinityStorageManager is not initialized.")); return 0; @@ -54,7 +55,7 @@ public class InfinityDiskGiveCommand { } catch (Exception ex) { source.sendFailure(Component.literal("Error: " + ex.getMessage())); return 0; - } + } } } diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityConstants.java b/src/main/java/com/extendedae_plus/util/storage/InfinityConstants.java new file mode 100644 index 0000000..1fcb598 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityConstants.java @@ -0,0 +1,30 @@ +package com.extendedae_plus.util.storage; + +public interface InfinityConstants { + // 当前磁盘格式版本号,增加字段用于向后/向前兼容 + int FORMAT_VERSION = 2; + // 存储磁盘数据的格式版本号 + String FORMAT_VERSION_FIELD = "infinity_format_version"; + + // savedData 文件名常量 + String SAVE_FILE_NAME = "infinity_biginteger_cells"; + + // 磁盘的唯一标识符键名,存储在 ItemStack 和 InfinityStorageManager 中 + String INFINITY_CELL_UUID = "infinity_cell_uuid"; + // 单个磁盘的 InfinityDataStorage 数据键名 + String INFINITY_CELL_DATA = "infinity_cell_data"; + // 所有磁盘数据的列表键名,存储在 InfinityStorageManager 的 NBT 中 + String INFINITY_CELL_LIST = "infinity_cell_list"; + + // 磁盘中所有物品键的键名(ListTag of CompoundTag) + String INFINITY_CELL_KEYS = "infinity_cell_keys"; + // 磁盘中每种物品数量的键名(ListTag of CompoundTag,包含 "value") + String INFINITY_CELL_AMOUNTS = "infinity_cell_amounts"; + // 磁盘中所有物品的总数键名(ListTag,包含一个 CompoundTag 的 "value") + String INFINITY_CELL_ITEM_COUNT = "infinity_cell_item_count"; + + // ItemStack 的 NBT 中存储总物品数量的键名 + String INFINITY_ITEM_TOTAL = "infinity_item_total"; + // ItemStack 的 NBT 中存储物品种类数量的键名 + String INFINITY_ITEM_TYPES = "infinity_item_types"; +} diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java index c56e421..6462128 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java @@ -2,62 +2,47 @@ package com.extendedae_plus.util.storage; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.Tag; + +import java.math.BigInteger; /** - * InfinityDataStorage - * - * 表示单个 UUID 对应的持久化数据容器,直接映射到世界存档中的一项记录。 - * 数据结构说明: - * - keys: 存放序列化后的 AEKey(每项为 CompoundTag),用于标识不同的存储条目 - * - amounts: 与 keys 一一对应的数量列表(每项为 CompoundTag),采用混合表示: - * - 当数量能放入 long 时,CompoundTag 包含键 "l" 存放 long 值 - * - 当数量超出 long 时,CompoundTag 包含键 "s" 存放 BigInteger 的字符串形式 - * - * 该类提供将内存数据与 NBT 之间互转的辅助方法,供 `SavedData` 在世界保存/加载时调用。 + * This code is inspired by AE2Things[](https://github.com/Technici4n/AE2Things-Forge), licensed under the MIT License.

+ * Original copyright (c) Technici4n

*/ public class InfinityDataStorage { + // 定义一个静态常量 EMPTY,表示一个空的 DataStorage 实例,用于默认或占位场景 + public static final InfinityDataStorage EMPTY = new InfinityDataStorage(); - /** 空实例的访问器(返回新实例以避免共享可变状态) */ - public static InfinityDataStorage empty() { - return new InfinityDataStorage(); - } - - /** 序列化的键列表(NBT ListTag,元素为 CompoundTag) */ public ListTag keys; - /** - * 与 keys 对应的数量列表(NBT ListTag,元素为 CompoundTag): - * - 若数量能放入 long,则 CompoundTag 包含键 "l"(long) - * - 否则包含键 "s"(String) 存放 BigInteger 的字符串形式 - */ public ListTag amounts; + // 存储磁盘中物品的总数,使用 BigInteger 支持大容量 + public BigInteger itemCount; public InfinityDataStorage() { - this(new ListTag(), new ListTag()); + this(new ListTag(), new ListTag(), BigInteger.ZERO); } - private InfinityDataStorage(ListTag keys, ListTag amounts) { + private InfinityDataStorage(ListTag keys, ListTag amounts, BigInteger itemCount) { this.keys = keys; this.amounts = amounts; + this.itemCount = itemCount; } - /** - * 将当前数据封装为 CompoundTag 以写入存档 - */ + // 将 DataStorage 数据序列化为 NBT 格式 public CompoundTag serializeNBT() { CompoundTag nbt = new CompoundTag(); - nbt.put("keys", keys); - nbt.put("amounts", amounts); + nbt.put(InfinityConstants.INFINITY_CELL_KEYS, keys); + nbt.put(InfinityConstants.INFINITY_CELL_AMOUNTS, amounts); + nbt.putByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT, itemCount.toByteArray()); return nbt; } - /** - * 从存档读取数据并构造实例 - */ + // 从 NBT 数据反序列化创建 DataStorage 实例 public static InfinityDataStorage loadFromNBT(CompoundTag nbt) { - ListTag stackKeys = nbt.getList("keys", Tag.TAG_COMPOUND); - // amounts 以 CompoundTag 列表存储,每个 CompoundTag 内含 long 或 String - ListTag stackAmounts = nbt.getList("amounts", Tag.TAG_COMPOUND); - return new InfinityDataStorage(stackKeys, stackAmounts); + ListTag keys = nbt.getList(InfinityConstants.INFINITY_CELL_KEYS, ListTag.TAG_COMPOUND); + ListTag amounts = nbt.getList(InfinityConstants.INFINITY_CELL_AMOUNTS, ListTag.TAG_COMPOUND); + BigInteger itemCount = new BigInteger(nbt.getByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT)); + // 使用加载的数据创建新的 DataStorage 实例 + return new InfinityDataStorage(keys, amounts, itemCount); } } diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java index 6ba1080..f27f2a6 100644 --- a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -2,145 +2,37 @@ package com.extendedae_plus.util.storage; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.NbtIo; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.saveddata.SavedData; -import net.minecraft.world.level.storage.LevelResource; import org.jetbrains.annotations.NotNull; -import java.io.File; -import java.io.IOException; -import java.nio.file.AtomicMoveNotSupportedException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import static com.extendedae_plus.util.ExtendedAELogger.LOGGER; +import java.math.BigInteger; +import java.util.*; /** - * InfinityStorageManager - *

- * 世界级别的持久化容器,集中管理所有 InfinityBigInteger 存储单元的序列化数据。 - * 功能要点: - * - 在世界加载时从存档恢复所有 cell 的数据 - * - 提供按 UUID 获取/创建单个 cell 的数据容器 - * - 在世界保存时将内存数据打包为 NBT 写回存档 + * This code is inspired by AE2Things[](https://github.com/Technici4n/AE2Things-Forge), licensed under the MIT License.

+ * Original copyright (c) Technici4n

*/ public class InfinityStorageManager extends SavedData { - // 当前磁盘格式版本号,增加字段用于向后/向前兼容 - private static final int FORMAT_VERSION = 1; + // 存储所有磁盘的Map,键为UUID,值为DataStorage对象 + private final Map cells; - /** - * SavedData 文件名常量 - */ - public static final String FILE_NAME = "eap_infinity_biginteger_cells"; - /** - * 全局单例实例(在世界加载时由 InfiniteBigIntegerStorageCell.onLevelLoad 填充) - */ - public static InfinityStorageManager INSTANCE = null; - /** - * UUID -> 数据 的内存映射 - */ - private final Map cells = new HashMap<>(); - - /** - * 返回当前已加载的所有 UUID 的不可变视图,用于命令或调试用途 - */ - public java.util.Set getAllLoadedUUIDs() { - return java.util.Collections.unmodifiableSet(cells.keySet()); - } - + // 构造方法,初始化磁盘Map public InfinityStorageManager() { - setDirty(); + cells = new HashMap<>(); + // 标记数据为“脏”,确保新创建的实例在下次保存时写入磁盘 + this.setDirty(); } - /** - * 从 NBT 构造:用于在世界加载时从存档恢复数据 - */ - public InfinityStorageManager(CompoundTag nbt) { - // 读取格式版本,缺省视为 1(兼容旧档) - int version = nbt.contains("format_version") ? nbt.getInt("format_version") : 1; - if (!nbt.contains("format_version")) { - // 旧档未包含 format_version,标记为需要写回以便下次保存写入版本号 - LOGGER.info("Found legacy InfinityStorageManager dat without format_version; treating as version {} and marking dirty for upgrade", version); - // 立即标记为脏以触发尽快写盘(升级写入 format_version) - this.setDirty(); - } else { - LOGGER.info("Loading InfinityStorageManager format_version={}", version); - } - - ListTag cellList = nbt.getList("list", CompoundTag.TAG_COMPOUND); - for (int i = 0; i < cellList.size(); i++) { - CompoundTag cell = cellList.getCompound(i); - java.util.UUID uuid = cell.getUUID("uuid"); - CompoundTag dataTag = cell.getCompound("data"); - InfinityDataStorage data = InfinityDataStorage.loadFromNBT(dataTag); - cells.put(uuid, data); - LOGGER.info("Loaded InfinityDataStorage for uuid {}: keys={}, amounts={}", uuid, data.keys.size(), data.amounts.size()); - } - setDirty(); - } - - /** - * 根据给定的 ServerLevel 获取或创建该世界对应的 SavedData 实例并缓存到 INSTANCE - */ - public static InfinityStorageManager getForLevel(ServerLevel level) { - if (INSTANCE == null && level != null) { - // 迁移逻辑:若旧的压缩 dat 文件存在且缺少 format_version,则读取并补上 version,备份原文件 - try { - Path dataDir = level.getServer().getWorldPath(new LevelResource("data")); - Path filePath = dataDir.resolve(FILE_NAME + ".dat"); - File oldFile = filePath.toFile(); - if (oldFile.exists()) { - // 检测是否为 gzip(压缩 NBT) - boolean isGzip = false; - try (var fis = Files.newInputStream(filePath)) { - int b1 = fis.read(); - int b2 = fis.read(); - isGzip = (b1 == 0x1F && b2 == 0x8B); - } catch (IOException ignored) { - } - if (isGzip) { - try { - var root = NbtIo.readCompressed(oldFile); - if (!root.contains("format_version")) { - // 备份旧文件 - Path bak = filePath.resolveSibling(FILE_NAME + ".dat.bak"); - try { - Files.copy(filePath, bak); - } catch (IOException ignored) { - } - // 写回并加入 format_version - root.putInt("format_version", FORMAT_VERSION); - Path tmp = filePath.resolveSibling(FILE_NAME + ".tmp"); - File tmpFile = tmp.toFile(); - if (tmpFile.getParentFile() != null && !tmpFile.getParentFile().exists()) { - tmpFile.getParentFile().mkdirs(); - } - NbtIo.writeCompressed(root, tmpFile); - try { - Files.move(tmp, filePath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); - } catch (AtomicMoveNotSupportedException ex) { - Files.move(tmp, filePath, StandardCopyOption.REPLACE_EXISTING); - } - LOGGER.info("Migrated old compressed {} to include format_version", filePath); - } - } catch (IOException ex) { - LOGGER.info("Failed to migrate old compressed file {}: {}", filePath, ex.getMessage()); - } - } - } - } catch (Throwable ignored) { - } - - INSTANCE = level.getDataStorage().computeIfAbsent(InfinityStorageManager::new, InfinityStorageManager::new, FILE_NAME); - } - return INSTANCE; + // 私有构造方法,用于从已有Map创建StorageManager + private InfinityStorageManager(Map cells) { + // 确保使用已加载的数据 + this.cells = cells; + // 标记数据为“脏”,确保新创建的实例在下次保存时写入磁盘 + this.setDirty(); } @Override @@ -149,83 +41,96 @@ public class InfinityStorageManager extends SavedData { ListTag cellList = new ListTag(); for (Map.Entry entry : cells.entrySet()) { CompoundTag cell = new CompoundTag(); - cell.putUUID("uuid", entry.getKey()); - cell.put("data", entry.getValue().serializeNBT()); + cell.putUUID(InfinityConstants.INFINITY_CELL_UUID, entry.getKey()); + cell.put(InfinityConstants.INFINITY_CELL_DATA, entry.getValue().serializeNBT()); cellList.add(cell); } - nbt.put("list", cellList); + nbt.put(InfinityConstants.INFINITY_CELL_LIST, cellList); // 写入当前格式版本号,便于未来迁移与兼容判断 - nbt.putInt("format_version", FORMAT_VERSION); + nbt.putInt(InfinityConstants.FORMAT_VERSION_FIELD, InfinityConstants.FORMAT_VERSION); return nbt; } - /** - * 更新或添加某个 UUID 对应的数据并标记为脏(需要保存) - */ + // 静态方法,从 NBT 数据反序列化创建 StorageManager 实例 + public static InfinityStorageManager readNbt(CompoundTag nbt) { + // 读取格式版本,缺省视为 1(兼容旧档) + int version = nbt.contains(InfinityConstants.FORMAT_VERSION_FIELD) ? + nbt.getInt(InfinityConstants.FORMAT_VERSION_FIELD) : + 1; + + Map cells = new HashMap<>(); + // 从 NBT 中获取磁盘数据列表,指定类型为 CompoundTag(TAG_COMPOUND) + ListTag cellList = nbt.getList(InfinityConstants.INFINITY_CELL_LIST, CompoundTag.TAG_COMPOUND); + // 遍历 cellList 中的每个 CompoundTag + for (int i = 0; i < cellList.size(); i++) { + // 获取当前索引的 CompoundTag,表示单个磁盘的数据 + CompoundTag cell = cellList.getCompound(i); + // 从 CompoundTag 中读取 UUID 和 DataStorage 数据,并存入 cells 映射 + cells.put(cell.getUUID(InfinityConstants.INFINITY_CELL_UUID), InfinityDataStorage.loadFromNBT(cell.getCompound(InfinityConstants.INFINITY_CELL_DATA))); + } + // 使用加载的 cells 数据创建新的 StorageManager 实例 + return new InfinityStorageManager(cells); + } + + // 返回当前已加载的所有 UUID 的不可变视图,用于命令或调试用途 + public Set getAllLoadedUUIDs() { + return Collections.unmodifiableSet(cells.keySet()); + } + + + // 更新或添加某个 UUID 对应的数据并标记为脏(需要保存) public void updateCell(UUID uuid, InfinityDataStorage infinityDataStorage) { cells.put(uuid, infinityDataStorage); + // 标记数据为“脏”,确保修改后的数据会在下次保存时写入磁盘 setDirty(); } - /** - * 获取或创建某个 UUID 对应的数据容器 - */ + // 删除某个 UUID 的持久化记录并标记为脏 + public void removeCell(UUID uuid) { + cells.remove(uuid); + // 标记数据为“脏”,确保移除操作会在下次保存时反映到磁盘 + setDirty(); + } + + // 检查指定 UUID 是否存在于 disks 映射中 + public boolean hasUUID(UUID uuid) { + // 返回 cells 映射是否包含指定 UUID + return cells.containsKey(uuid); + } + + // 获取或创建某个 UUID 对应的数据容器 public InfinityDataStorage getOrCreateCell(UUID uuid) { + // 检查 cells 映射中是否不存在指定 UUID if (!cells.containsKey(uuid)) { updateCell(uuid, new InfinityDataStorage()); } + // 返回指定 UUID 对应的 DataStorage 对象 return cells.get(uuid); } - /** - * 修改某个 UUID 对应的键与数量列表并保存(新的签名,stackAmounts 为 ListTag 字符串列表) - */ - public void modifyCell(UUID cellID, ListTag stackKeys, ListTag stackAmounts) { - InfinityDataStorage cellToModify = getOrCreateCell(cellID); - if (stackKeys != null && stackAmounts != null) { - cellToModify.keys = stackKeys; - cellToModify.amounts = stackAmounts; + // 修改指定 UUID 的磁盘数据,包括堆栈键、数量和总项目数 + public void modifyDisk(UUID uuid, ListTag keys, ListTag amounts, BigInteger itemCount) { + // 获取或创建指定 UUID 的 DataStorage 对象 + InfinityDataStorage cellToModify = getOrCreateCell(uuid); + if (keys != null && amounts != null) { + cellToModify.keys = keys; + cellToModify.amounts = amounts; } - updateCell(cellID, cellToModify); + // 更新 DataStorage 的 itemCount 字段 + cellToModify.itemCount = itemCount; + // 将修改后的 DataStorage 对象更新到 cells 映射 + updateCell(uuid, cellToModify); } - /** - * 删除某个 UUID 的持久化记录并标记为脏 - */ - public void removeCell(UUID uuid) { - cells.remove(uuid); - setDirty(); - } - - /** - * 强制将内存中的所有 InfinityDataStorage 写入磁盘 - * 可在服务器停止或 tick 中调用,确保数据不会丢失 - */ - public void forceSaveAll(ServerLevel level) { - try { - if (level != null) { - this.setDirty(); - CompoundTag nbt = new CompoundTag(); - this.save(nbt); - File file = level.getServer().getWorldPath(new LevelResource("data")) - .resolve(FILE_NAME + ".dat").toFile(); - NbtIo.writeCompressed(nbt, file); - - // 打印所有已保存的无限磁盘 UUID 及相关信息 - StringBuilder sb = new StringBuilder(); - sb.append("Saving Infinity Disks (UUIDs and info):\n"); - for (Map.Entry entry : cells.entrySet()) { - UUID uuid = entry.getKey(); - InfinityDataStorage data = entry.getValue(); - int types = (data.keys != null) ? data.keys.size() : 0; - sb.append(" - UUID: ").append(uuid) - .append(", Types: ").append(types) - .append("\n"); - } - LOGGER.info(sb.toString()); - } - } catch (Throwable ex) { - LOGGER.info("InfinityStorageManager forceSaveAll error: {}", ex.getMessage()); - } + // 静态方法,获取 StorageManager 的单例实例 + public static InfinityStorageManager getInstance(MinecraftServer server) { + ServerLevel world = server.getLevel(ServerLevel.OVERWORLD); + // 使用 DataStorage 的 computeIfAbsent 方法加载或创建 StorageManager 实例 + // 如果数据存在,则调用 readNbt 加载;否则调用默认构造器创建新实例 + return world.getDataStorage().computeIfAbsent( + InfinityStorageManager::readNbt, + InfinityStorageManager::new, + InfinityConstants.SAVE_FILE_NAME + ); } } From 6b8590c2f29f6e466747ad101f4b75488466eed4 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Tue, 23 Sep 2025 18:38:13 +0800 Subject: [PATCH 25/34] =?UTF-8?q?=E5=BD=93appflux=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E6=97=B6=E4=B8=BA=E6=A0=B7=E6=9D=BF=E4=BE=9B=E5=BA=94=E5=99=A8?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=80=E4=B8=AA=E5=8D=87=E7=BA=A7=E6=A7=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- .../java/com/extendedae_plus/mixin/MixinConditions.java | 6 ++++++ src/main/resources/extendedae_plus.mixins.json | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f22d7c4..25fe6a4 100644 --- a/build.gradle +++ b/build.gradle @@ -81,7 +81,7 @@ dependencies { annotationProcessor "org.spongepowered:mixin:${mixin_version}:processor" - // modImplementation "curse.maven:applied-flux-965012:6755986" + modImplementation "curse.maven:applied-flux-965012:6755986" modCompileOnly "curse.maven:mega-cells-622112:${mega_cells_version}" modCompileOnly "curse.maven:jade-324717:${jade_version}" diff --git a/src/main/java/com/extendedae_plus/mixin/MixinConditions.java b/src/main/java/com/extendedae_plus/mixin/MixinConditions.java index ed45cbe..30c8213 100644 --- a/src/main/java/com/extendedae_plus/mixin/MixinConditions.java +++ b/src/main/java/com/extendedae_plus/mixin/MixinConditions.java @@ -52,6 +52,12 @@ public class MixinConditions implements IMixinConfigPlugin { } } + // 对于appflux相关的Mixin,总是加载但在运行时检查条件 + if (mixinClassName.contains("AppfluxPatternProviderLogicMixin")) { + System.out.println("[ExtendedAE_Plus] 总是加载appflux Mixin,运行时检查条件: " + mixinClassName); + return true; // 总是加载,在Mixin内部进行运行时检查 + } + // 其他Mixin正常应用 System.out.println("[ExtendedAE_Plus] 加载Mixin: " + mixinClassName); return true; diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index 5c81819..c266b12 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -66,6 +66,7 @@ "ae2.compat.PatternProviderLogicCompatMixin", "ae2.compat.PatternProviderLogicHostCompatMixin", "ae2.compat.PatternProviderCompatMixin", + "appflux.AppfluxPatternProviderLogicMixin", "ae2.helpers.patternprovider.PatternProviderLogicTickerMixin", "ae2.parts.AEBasePartClientSyncMixin", "ae2.parts.automation.IOBusPartChannelCardMixin", From 66b581c2a702858f1289c8a77d5ed53039a019c3 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Tue, 23 Sep 2025 18:53:27 +0800 Subject: [PATCH 26/34] =?UTF-8?q?=E4=B8=BAme=E6=8E=A5=E5=8F=A3=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=B8=80=E4=B8=AA=E5=8D=87=E7=BA=A7=E6=A7=BD=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E4=B8=94=E5=85=BC=E5=AE=B9applied=20flux?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- .../com/extendedae_plus/compat/UpgradeSlotCompat.java | 10 +++++++++- .../com/extendedae_plus/mixin/MixinConditions.java | 6 ++++++ src/main/resources/extendedae_plus.mixins.json | 3 ++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 25fe6a4..f22d7c4 100644 --- a/build.gradle +++ b/build.gradle @@ -81,7 +81,7 @@ dependencies { annotationProcessor "org.spongepowered:mixin:${mixin_version}:processor" - modImplementation "curse.maven:applied-flux-965012:6755986" + // modImplementation "curse.maven:applied-flux-965012:6755986" modCompileOnly "curse.maven:mega-cells-622112:${mega_cells_version}" modCompileOnly "curse.maven:jade-324717:${jade_version}" diff --git a/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java b/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java index cef62a5..9768050 100644 --- a/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java +++ b/src/main/java/com/extendedae_plus/compat/UpgradeSlotCompat.java @@ -27,12 +27,20 @@ import java.util.List; public class UpgradeSlotCompat { private static final String APPFLUX_MOD_ID = "appflux"; + /** + * 检测Applied Flux模组是否存在 + * @return true如果存在,false如果不存在 + */ + public static boolean isAppfluxPresent() { + return ModList.get().isLoaded(APPFLUX_MOD_ID); + } + /** * 检测是否应该启用我们的升级卡槽功能 * @return true如果应该启用,false如果检测到appflux模组存在 */ public static boolean shouldEnableUpgradeSlots() { - boolean appfluxExists = ModList.get().isLoaded(APPFLUX_MOD_ID); + boolean appfluxExists = isAppfluxPresent(); ExtendedAELogger.LOGGER.info("ExtendedAE-appflux模组检测: {}", appfluxExists ? "存在" : "不存在"); if (appfluxExists) { diff --git a/src/main/java/com/extendedae_plus/mixin/MixinConditions.java b/src/main/java/com/extendedae_plus/mixin/MixinConditions.java index 30c8213..7a47b91 100644 --- a/src/main/java/com/extendedae_plus/mixin/MixinConditions.java +++ b/src/main/java/com/extendedae_plus/mixin/MixinConditions.java @@ -58,6 +58,12 @@ public class MixinConditions implements IMixinConfigPlugin { return true; // 总是加载,在Mixin内部进行运行时检查 } + // 对于InterfaceLogicUpgradesMixin,总是加载但在运行时检查条件 + if (mixinClassName.contains("InterfaceLogicUpgradesMixin")) { + System.out.println("[ExtendedAE_Plus] 总是加载Interface升级Mixin,运行时检查条件: " + mixinClassName); + return true; // 总是加载,在Mixin内部进行运行时检查 + } + // 其他Mixin正常应用 System.out.println("[ExtendedAE_Plus] 加载Mixin: " + mixinClassName); return true; diff --git a/src/main/resources/extendedae_plus.mixins.json b/src/main/resources/extendedae_plus.mixins.json index c266b12..97c7ce5 100644 --- a/src/main/resources/extendedae_plus.mixins.json +++ b/src/main/resources/extendedae_plus.mixins.json @@ -80,7 +80,8 @@ "extendedae.container.ContainerExPatternTerminalMixin", "extendedae.container.ContainerWirelessExPatternTerminalMixin", "ae2.helpers.InterfaceLogicChannelCardMixin", - "ae2.helpers.InterfaceLogicTickerMixin" + "ae2.helpers.InterfaceLogicTickerMixin", + "ae2.InterfaceLogicUpgradesMixin" ], "injectors": { "defaultRequire": 1 From 476b66947274f51d1bf8919d89d8d3fc2a313341 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Tue, 23 Sep 2025 19:03:23 +0800 Subject: [PATCH 27/34] =?UTF-8?q?=E4=B8=BAme=E6=8E=A5=E5=8F=A3=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=B8=80=E4=B8=AA=E5=8D=87=E7=BA=A7=E6=A7=BD=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E4=B8=94=E5=85=BC=E5=AE=B9applied=20flux?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- .../ae2/InterfaceLogicUpgradesMixin.java | 80 ++++++ ...PatternProviderLogicHostUpgradesMixin.java | 24 -- .../PatternProviderLogicUpgradesMixin.java | 272 ------------------ .../AppfluxPatternProviderLogicMixin.java | 79 +++++ 5 files changed, 160 insertions(+), 297 deletions(-) create mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/InterfaceLogicUpgradesMixin.java delete mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicHostUpgradesMixin.java delete mode 100644 src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java create mode 100644 src/main/java/com/extendedae_plus/mixin/appflux/AppfluxPatternProviderLogicMixin.java diff --git a/build.gradle b/build.gradle index f22d7c4..25fe6a4 100644 --- a/build.gradle +++ b/build.gradle @@ -81,7 +81,7 @@ dependencies { annotationProcessor "org.spongepowered:mixin:${mixin_version}:processor" - // modImplementation "curse.maven:applied-flux-965012:6755986" + modImplementation "curse.maven:applied-flux-965012:6755986" modCompileOnly "curse.maven:mega-cells-622112:${mega_cells_version}" modCompileOnly "curse.maven:jade-324717:${jade_version}" diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/InterfaceLogicUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/InterfaceLogicUpgradesMixin.java new file mode 100644 index 0000000..0bc4575 --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/ae2/InterfaceLogicUpgradesMixin.java @@ -0,0 +1,80 @@ +package com.extendedae_plus.mixin.ae2; + +import appeng.api.networking.IManagedGridNode; +import appeng.api.upgrades.IUpgradeInventory; +import appeng.api.upgrades.UpgradeInventories; +import appeng.helpers.InterfaceLogic; +import appeng.helpers.InterfaceLogicHost; +import com.extendedae_plus.compat.UpgradeSlotCompat; +import net.minecraft.world.item.Item; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +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; + +/** + * 为ME接口增加升级槽数量的Mixin + * 兼容Applied Flux模组,避免冲突 + */ +@Mixin(value = InterfaceLogic.class, remap = false, priority = 1500) +public class InterfaceLogicUpgradesMixin { + + @Final + @Mutable + @Shadow + private IUpgradeInventory upgrades; + + @Shadow + protected void onUpgradesChanged() {} + + /** + * 在InterfaceLogic构造函数末尾注入,增加升级槽数量 + * 使用高优先级(1500)确保在Applied Flux之后执行 + */ + @Inject( + method = "(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/InterfaceLogicHost;Lnet/minecraft/world/item/Item;I)V", + at = @At("TAIL") + ) + private void expandInterfaceUpgrades(IManagedGridNode gridNode, InterfaceLogicHost host, Item is, int slots, CallbackInfo ci) { + try { + System.out.println("[ExtendedAE_Plus] InterfaceLogic升级槽扩展开始"); + + // 检查Applied Flux是否已经修改了升级槽 + if (UpgradeSlotCompat.isAppfluxPresent()) { + System.out.println("[ExtendedAE_Plus] 检测到Applied Flux存在"); + // Applied Flux存在,检查当前升级槽数量 + int currentSlots = this.upgrades.size(); + System.out.println("[ExtendedAE_Plus] 当前升级槽数量: " + currentSlots); + + if (currentSlots >= 3) { + // Applied Flux已经增加了升级槽到3个或更多,我们不需要再修改 + System.out.println("[ExtendedAE_Plus] Applied Flux已经增加了足够的升级槽,跳过修改"); + return; + } else if (currentSlots == 2) { + // Applied Flux增加到2个,我们再增加1个到3个 + System.out.println("[ExtendedAE_Plus] Applied Flux增加到2个槽,我们再增加到3个"); + this.upgrades = UpgradeInventories.forMachine(is, 3, this::onUpgradesChanged); + } else { + // Applied Flux可能还没有生效,我们直接增加到3个 + System.out.println("[ExtendedAE_Plus] Applied Flux可能未生效,直接增加到3个槽"); + this.upgrades = UpgradeInventories.forMachine(is, 3, this::onUpgradesChanged); + } + } else { + System.out.println("[ExtendedAE_Plus] Applied Flux不存在"); + // Applied Flux不存在,我们将升级槽从1个增加到2个 + int currentSlots = this.upgrades.size(); + System.out.println("[ExtendedAE_Plus] 当前升级槽数量: " + currentSlots + ",增加到2个"); + this.upgrades = UpgradeInventories.forMachine(is, 2, this::onUpgradesChanged); + } + + System.out.println("[ExtendedAE_Plus] InterfaceLogic升级槽扩展完成,最终槽数: " + this.upgrades.size()); + } catch (Exception e) { + // 发生异常时不修改升级槽,确保不会崩溃 + System.err.println("[ExtendedAE_Plus] Failed to expand interface upgrades: " + e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicHostUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicHostUpgradesMixin.java deleted file mode 100644 index 47f27dc..0000000 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicHostUpgradesMixin.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.extendedae_plus.mixin.ae2.helpers.patternprovider; - -import appeng.api.upgrades.IUpgradeInventory; -import appeng.api.upgrades.IUpgradeableObject; -import appeng.helpers.patternprovider.PatternProviderLogic; -import appeng.helpers.patternprovider.PatternProviderLogicHost; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -/** - * 让 PatternProviderLogicHost 作为 IUpgradeableObject 的代理,菜单可从 host 获取升级槽。 - */ -@Mixin(value = PatternProviderLogicHost.class, priority = 2000, remap = false) -public interface PatternProviderLogicHostUpgradesMixin extends IUpgradeableObject { - @Shadow PatternProviderLogic getLogic(); - - @Override - default IUpgradeInventory getUpgrades() { - if (!com.extendedae_plus.compat.UpgradeSlotCompat.shouldEnableUpgradeSlots()) { - return appeng.api.upgrades.UpgradeInventories.empty(); - } - return ((IUpgradeableObject) this.getLogic()).getUpgrades(); - } -} diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java deleted file mode 100644 index 59f06c9..0000000 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/patternprovider/PatternProviderLogicUpgradesMixin.java +++ /dev/null @@ -1,272 +0,0 @@ -package com.extendedae_plus.mixin.ae2.helpers.patternprovider; - -import appeng.api.networking.IManagedGridNode; -import appeng.api.networking.security.IActionSource; -import appeng.api.upgrades.IUpgradeInventory; -import appeng.api.upgrades.IUpgradeableObject; -import appeng.api.upgrades.UpgradeInventories; -import appeng.helpers.patternprovider.PatternProviderLogic; -import appeng.helpers.patternprovider.PatternProviderLogicHost; -import com.extendedae_plus.ae.items.ChannelCardItem; -import com.extendedae_plus.compat.UpgradeSlotCompat; -import com.extendedae_plus.init.ModItems; -import com.extendedae_plus.wireless.WirelessSlaveLink; -import com.extendedae_plus.wireless.endpoint.GenericNodeEndpointImpl; -import com.extendedae_plus.bridge.InterfaceWirelessLinkBridge; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -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; - -import net.minecraft.nbt.CompoundTag; -import net.minecraft.world.item.ItemStack; - -import java.util.List; - -/** - * 为 PatternProviderLogic 注入升级槽,实现 IUpgradeableObject。 - * 仅负责升级槽的持久化/掉落/清空与初始化,不改变原有逻辑。 - */ -@Mixin(value = PatternProviderLogic.class, priority = 2000, remap = false) -public abstract class PatternProviderLogicUpgradesMixin implements IUpgradeableObject, InterfaceWirelessLinkBridge { - @Unique - private IUpgradeInventory eap$upgrades = UpgradeInventories.empty(); - - @Unique - private WirelessSlaveLink eap$link; - - @Unique - private long eap$lastChannel = -1; - - @Unique - private boolean eap$clientConnected = false; - - @Unique - private boolean eap$hasInitialized = false; - - @Unique - private int eap$delayedInitTicks = 0; - - @Final - @Shadow - private PatternProviderLogicHost host; - - @Final - @Shadow - private IManagedGridNode mainNode; - - @Final - @Shadow - private IActionSource actionSource; - - @Unique - private void eap$onUpgradesChanged() { - this.host.saveChanges(); - // 升级变更,重置并尝试初始化 - eap$lastChannel = -1; - eap$hasInitialized = false; - eap$initializeChannelLink(); - } - - - - @Inject(method = "(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/patternprovider/PatternProviderLogicHost;I)V", - at = @At("TAIL")) - private void eap$initUpgrades(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) { - // 只有在应该启用升级卡槽时才初始化 - if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { - return; - } - - this.eap$upgrades = UpgradeInventories.forMachine(host.getTerminalIcon().getItem(), 1, this::eap$onUpgradesChanged); - } - - @Inject(method = "writeToNBT", at = @At("TAIL")) - private void eap$saveUpgrades(CompoundTag tag, CallbackInfo ci) { - if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { - return; - } - this.eap$upgrades.writeToNBT(tag, "upgrades"); - } - - @Inject(method = "readFromNBT", at = @At("TAIL")) - private void eap$loadUpgrades(CompoundTag tag, CallbackInfo ci) { - if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { - return; - } - this.eap$upgrades.readFromNBT(tag, "upgrades"); - // 从 NBT 加载后,重置并尝试初始化(可能刚进入世界) - eap$lastChannel = -1; - eap$hasInitialized = false; - eap$initializeChannelLink(); - } - - @Inject(method = "addDrops", at = @At("TAIL")) - private void eap$dropUpgrades(List drops, CallbackInfo ci) { - if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { - return; - } - for (var stack : this.eap$upgrades) { - if (!stack.isEmpty()) { - drops.add(stack); - } - } - } - - @Inject(method = "clearContent", at = @At("TAIL")) - private void eap$clearUpgrades(CallbackInfo ci) { - if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { - return; - } - this.eap$upgrades.clear(); - } - - @Override - public void eap$updateWirelessLink() { - if (eap$link != null) { - eap$link.updateStatus(); - } - } - - @Override - public IUpgradeInventory getUpgrades() { - if (!UpgradeSlotCompat.shouldEnableUpgradeSlots()) { - return UpgradeInventories.empty(); - } - return this.eap$upgrades; - } - - // ===== 频道卡初始化与延迟重试(服务端) ===== - @Unique - public void eap$initializeChannelLink() { - // 客户端早退 - if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) { - return; - } - - // 避免重复初始化 - if (eap$hasInitialized) { - return; - } - - // 等待网格完成引导 - if (!mainNode.hasGridBooted()) { - // 安排短延迟,等待后续 tick 再试 - eap$delayedInitTicks = Math.max(eap$delayedInitTicks, 5); - try { - mainNode.ifPresent((grid, node) -> { - try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {} - }); - } catch (Throwable ignored) {} - return; - } - - try { - long channel = 0L; - boolean found = false; - for (ItemStack stack : this.eap$upgrades) { - if (!stack.isEmpty() && stack.getItem() == ModItems.CHANNEL_CARD.get()) { - channel = ChannelCardItem.getChannel(stack); - found = true; - break; - } - } - - if (!found) { - // 无频道卡:断开并视为初始化完成 - if (eap$link != null) { - eap$link.setFrequency(0L); - eap$link.updateStatus(); - } - eap$hasInitialized = true; - return; - } - - if (eap$link == null) { - var endpoint = new GenericNodeEndpointImpl(() -> host.getBlockEntity(), () -> this.mainNode.getNode()); - eap$link = new WirelessSlaveLink(endpoint); - } - - eap$link.setFrequency(channel); - eap$link.updateStatus(); - - if (eap$link.isConnected()) { - eap$hasInitialized = true; - } else { - eap$hasInitialized = false; - eap$delayedInitTicks = Math.max(eap$delayedInitTicks, 5); - try { - mainNode.ifPresent((grid, node) -> { - try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {} - }); - } catch (Throwable ignored) {} - } - } catch (Exception ignored) { - } - } - - @Override - public void eap$setClientWirelessState(boolean connected) { - eap$clientConnected = connected; - } - - @Override - public boolean eap$isWirelessConnected() { - if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) { - return eap$clientConnected; - } else { - return eap$link != null && eap$link.isConnected(); - } - } - - @Override - public boolean eap$hasTickInitialized() { - return eap$hasInitialized; - } - - @Override - public void eap$setTickInitialized(boolean initialized) { - eap$hasInitialized = initialized; - } - - @Override - public void eap$handleDelayedInit() { - // 仅服务端 - if (host.getBlockEntity() != null && host.getBlockEntity().getLevel() != null && host.getBlockEntity().getLevel().isClientSide) { - return; - } - if (!eap$hasInitialized) { - if (!mainNode.hasGridBooted()) { - if (eap$delayedInitTicks > 0) { - eap$delayedInitTicks--; - } - if (eap$delayedInitTicks == 0) { - eap$delayedInitTicks = 5; - try { - mainNode.ifPresent((grid, node) -> { - try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {} - }); - } catch (Throwable ignored) {} - } - } else { - eap$initializeChannelLink(); - } - } - } - - // 当主节点状态变化(例如 GRID_BOOT 完成)时,安排一次延迟初始化并唤醒设备 - @Inject(method = "onMainNodeStateChanged", at = @At("TAIL")) - private void eap$onMainNodeStateChangedTail(CallbackInfo ci) { - eap$lastChannel = -1; - eap$hasInitialized = false; - eap$delayedInitTicks = 10; - try { - mainNode.ifPresent((grid, node) -> { - try { grid.getTickManager().wakeDevice(node); } catch (Throwable ignored) {} - }); - } catch (Throwable ignored) {} - } -} diff --git a/src/main/java/com/extendedae_plus/mixin/appflux/AppfluxPatternProviderLogicMixin.java b/src/main/java/com/extendedae_plus/mixin/appflux/AppfluxPatternProviderLogicMixin.java new file mode 100644 index 0000000..201121e --- /dev/null +++ b/src/main/java/com/extendedae_plus/mixin/appflux/AppfluxPatternProviderLogicMixin.java @@ -0,0 +1,79 @@ +package com.extendedae_plus.mixin.appflux; + +import appeng.api.networking.IManagedGridNode; +import appeng.api.upgrades.IUpgradeInventory; +import appeng.api.upgrades.UpgradeInventories; +import appeng.helpers.patternprovider.PatternProviderLogic; +import appeng.helpers.patternprovider.PatternProviderLogicHost; +import com.extendedae_plus.compat.UpgradeSlotCompat; +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.lang.reflect.Field; + +/** + * 当appflux存在时,修改PatternProviderLogic的升级槽数量为2个 + * 优先级设置为2000,确保在appflux之后应用 + */ +@Mixin(value = PatternProviderLogic.class, priority = 2000, remap = false) +public class AppfluxPatternProviderLogicMixin { + + /** + * 在appflux初始化升级槽之后,替换为2个槽的版本 + */ + @Inject(method = "(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/patternprovider/PatternProviderLogicHost;I)V", + at = @At("TAIL")) + private void eap$modifyAppfluxUpgradeSlots(IManagedGridNode mainNode, PatternProviderLogicHost host, int patternInventorySize, CallbackInfo ci) { + try { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("AppfluxPatternProviderLogicMixin被调用!"); + + // 只有当appflux存在且不启用我们的升级槽时才修改数量 + if (!UpgradeSlotCompat.shouldEnableUpgradeSlots() && UpgradeSlotCompat.shouldEnableChannelCard()) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("尝试修改appflux升级槽数量为2个"); + + // 使用反射找到appflux的升级槽字段并替换 + try { + Field upgradesField = this.getClass().getDeclaredField("af_$upgrades"); + upgradesField.setAccessible(true); + IUpgradeInventory currentUpgrades = (IUpgradeInventory) upgradesField.get(this); + + if (currentUpgrades != null) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("找到appflux升级槽,当前大小: {}", currentUpgrades.size()); + + // 创建新的2槽升级槽 + IUpgradeInventory newUpgrades = UpgradeInventories.forMachine( + host.getTerminalIcon().getItem(), + 2, + () -> { + try { + // 调用appflux的升级变更方法 + this.getClass().getDeclaredMethod("af_$onUpgradesChanged").invoke(this); + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("调用appflux升级变更方法失败", e); + } + } + ); + + // 复制原有升级卡到新的升级槽 + for (int i = 0; i < Math.min(currentUpgrades.size(), newUpgrades.size()); i++) { + if (!currentUpgrades.getStackInSlot(i).isEmpty()) { + newUpgrades.insertItem(i, currentUpgrades.getStackInSlot(i).copy(), false); + } + } + + // 替换升级槽 + upgradesField.set(this, newUpgrades); + com.extendedae_plus.util.ExtendedAELogger.LOGGER.info("成功将appflux升级槽替换为2个槽"); + } + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("反射修改appflux升级槽失败", e); + } + } + } catch (Exception e) { + com.extendedae_plus.util.ExtendedAELogger.LOGGER.error("AppfluxPatternProviderLogicMixin执行失败", e); + } + } +} From 039fdc3f90913f1a5cbd04fcc738cf4985188e39 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Tue, 23 Sep 2025 19:08:20 +0800 Subject: [PATCH 28/34] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ae2/InterfaceLogicUpgradesMixin.java | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/InterfaceLogicUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/InterfaceLogicUpgradesMixin.java index 0bc4575..bf3bb9b 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/InterfaceLogicUpgradesMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/InterfaceLogicUpgradesMixin.java @@ -19,7 +19,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; * 为ME接口增加升级槽数量的Mixin * 兼容Applied Flux模组,避免冲突 */ -@Mixin(value = InterfaceLogic.class, remap = false, priority = 1500) +@Mixin(value = InterfaceLogic.class, remap = false, priority = 1100) public class InterfaceLogicUpgradesMixin { @Final @@ -32,22 +32,34 @@ public class InterfaceLogicUpgradesMixin { /** * 在InterfaceLogic构造函数末尾注入,增加升级槽数量 - * 使用高优先级(1500)确保在Applied Flux之后执行 + * 使用优先级1100确保在Applied Flux之后执行,但不会过度干扰其他组件 */ @Inject( method = "(Lappeng/api/networking/IManagedGridNode;Lappeng/helpers/InterfaceLogicHost;Lnet/minecraft/world/item/Item;I)V", - at = @At("TAIL") + at = @At("TAIL"), + require = 0 // 设置为可选注入,避免在某些情况下导致崩溃 ) private void expandInterfaceUpgrades(IManagedGridNode gridNode, InterfaceLogicHost host, Item is, int slots, CallbackInfo ci) { try { + // 添加更多的安全检查 + if (this.upgrades == null) { + System.err.println("[ExtendedAE_Plus] InterfaceLogic upgrades为null,跳过升级槽扩展"); + return; + } + + if (gridNode == null || host == null || is == null) { + System.err.println("[ExtendedAE_Plus] InterfaceLogic构造参数为null,跳过升级槽扩展"); + return; + } + System.out.println("[ExtendedAE_Plus] InterfaceLogic升级槽扩展开始"); + int currentSlots = this.upgrades.size(); + System.out.println("[ExtendedAE_Plus] 当前升级槽数量: " + currentSlots); + // 检查Applied Flux是否已经修改了升级槽 if (UpgradeSlotCompat.isAppfluxPresent()) { System.out.println("[ExtendedAE_Plus] 检测到Applied Flux存在"); - // Applied Flux存在,检查当前升级槽数量 - int currentSlots = this.upgrades.size(); - System.out.println("[ExtendedAE_Plus] 当前升级槽数量: " + currentSlots); if (currentSlots >= 3) { // Applied Flux已经增加了升级槽到3个或更多,我们不需要再修改 @@ -57,17 +69,25 @@ public class InterfaceLogicUpgradesMixin { // Applied Flux增加到2个,我们再增加1个到3个 System.out.println("[ExtendedAE_Plus] Applied Flux增加到2个槽,我们再增加到3个"); this.upgrades = UpgradeInventories.forMachine(is, 3, this::onUpgradesChanged); - } else { + } else if (currentSlots == 1) { // Applied Flux可能还没有生效,我们直接增加到3个 - System.out.println("[ExtendedAE_Plus] Applied Flux可能未生效,直接增加到3个槽"); + System.out.println("[ExtendedAE_Plus] Applied Flux存在但未生效,直接增加到3个槽"); this.upgrades = UpgradeInventories.forMachine(is, 3, this::onUpgradesChanged); + } else { + System.out.println("[ExtendedAE_Plus] Applied Flux存在,当前槽数异常: " + currentSlots + ",跳过修改"); + return; } } else { System.out.println("[ExtendedAE_Plus] Applied Flux不存在"); - // Applied Flux不存在,我们将升级槽从1个增加到2个 - int currentSlots = this.upgrades.size(); - System.out.println("[ExtendedAE_Plus] 当前升级槽数量: " + currentSlots + ",增加到2个"); - this.upgrades = UpgradeInventories.forMachine(is, 2, this::onUpgradesChanged); + + if (currentSlots == 1) { + // Applied Flux不存在,我们将升级槽从1个增加到2个 + System.out.println("[ExtendedAE_Plus] 将升级槽从1个增加到2个"); + this.upgrades = UpgradeInventories.forMachine(is, 2, this::onUpgradesChanged); + } else { + System.out.println("[ExtendedAE_Plus] Applied Flux不存在,当前槽数异常: " + currentSlots + ",跳过修改"); + return; + } } System.out.println("[ExtendedAE_Plus] InterfaceLogic升级槽扩展完成,最终槽数: " + this.upgrades.size()); From bb6bb610019ae694f2eaba876c5f644c1aabecf5 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Tue, 23 Sep 2025 19:14:32 +0800 Subject: [PATCH 29/34] =?UTF-8?q?=E5=88=A0=E9=99=A4debug=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 2 +- .../AdvPatternProviderLogicAdvancedMixin.java | 1 - .../AdvPatternProviderLogicDoublingMixin.java | 1 - .../ae2/InterfaceLogicUpgradesMixin.java | 37 +++---------------- .../PatternProviderLogicAdvancedMixin.java | 1 - .../PatternProviderLogicDoublingMixin.java | 1 - 6 files changed, 6 insertions(+), 37 deletions(-) diff --git a/gradle.properties b/gradle.properties index 32815b9..ba9b599 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G loom.platform = forge # Mod properties -mod_version = 1.4.2-fix +mod_version = 1.4.3 maven_group = com.extendedae_plus archives_name = extendedae_plus diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicAdvancedMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicAdvancedMixin.java index 8df0927..2ae2419 100644 --- a/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicAdvancedMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicAdvancedMixin.java @@ -94,7 +94,6 @@ public class AdvPatternProviderLogicAdvancedMixin implements AdvancedBlockingHol @Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL")) private void onExportSettings(CompoundTag output, CallbackInfo ci) { - System.out.println(this.eap$advancedBlocking); output.putBoolean(EAP_ADV_BLOCKING_KEY, this.eap$advancedBlocking); } diff --git a/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java index 85f7041..5436f6f 100644 --- a/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/advancedae/helpers/AdvPatternProviderLogicDoublingMixin.java @@ -76,7 +76,6 @@ public class AdvPatternProviderLogicDoublingMixin implements SmartDoublingHolder @Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL")) private void onExportSettings(CompoundTag output, CallbackInfo ci) { - System.out.println(this.eap$smartDoubling); output.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling); } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/InterfaceLogicUpgradesMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/InterfaceLogicUpgradesMixin.java index bf3bb9b..cdecdfa 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/InterfaceLogicUpgradesMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/InterfaceLogicUpgradesMixin.java @@ -41,60 +41,33 @@ public class InterfaceLogicUpgradesMixin { ) private void expandInterfaceUpgrades(IManagedGridNode gridNode, InterfaceLogicHost host, Item is, int slots, CallbackInfo ci) { try { - // 添加更多的安全检查 - if (this.upgrades == null) { - System.err.println("[ExtendedAE_Plus] InterfaceLogic upgrades为null,跳过升级槽扩展"); + // 安全检查 + if (this.upgrades == null || gridNode == null || host == null || is == null) { return; } - if (gridNode == null || host == null || is == null) { - System.err.println("[ExtendedAE_Plus] InterfaceLogic构造参数为null,跳过升级槽扩展"); - return; - } - - System.out.println("[ExtendedAE_Plus] InterfaceLogic升级槽扩展开始"); - int currentSlots = this.upgrades.size(); - System.out.println("[ExtendedAE_Plus] 当前升级槽数量: " + currentSlots); // 检查Applied Flux是否已经修改了升级槽 if (UpgradeSlotCompat.isAppfluxPresent()) { - System.out.println("[ExtendedAE_Plus] 检测到Applied Flux存在"); - if (currentSlots >= 3) { - // Applied Flux已经增加了升级槽到3个或更多,我们不需要再修改 - System.out.println("[ExtendedAE_Plus] Applied Flux已经增加了足够的升级槽,跳过修改"); + // Applied Flux已经增加了足够的升级槽,跳过修改 return; } else if (currentSlots == 2) { // Applied Flux增加到2个,我们再增加1个到3个 - System.out.println("[ExtendedAE_Plus] Applied Flux增加到2个槽,我们再增加到3个"); this.upgrades = UpgradeInventories.forMachine(is, 3, this::onUpgradesChanged); } else if (currentSlots == 1) { - // Applied Flux可能还没有生效,我们直接增加到3个 - System.out.println("[ExtendedAE_Plus] Applied Flux存在但未生效,直接增加到3个槽"); + // Applied Flux存在但未生效,直接增加到3个 this.upgrades = UpgradeInventories.forMachine(is, 3, this::onUpgradesChanged); - } else { - System.out.println("[ExtendedAE_Plus] Applied Flux存在,当前槽数异常: " + currentSlots + ",跳过修改"); - return; } } else { - System.out.println("[ExtendedAE_Plus] Applied Flux不存在"); - if (currentSlots == 1) { - // Applied Flux不存在,我们将升级槽从1个增加到2个 - System.out.println("[ExtendedAE_Plus] 将升级槽从1个增加到2个"); + // Applied Flux不存在,将升级槽从1个增加到2个 this.upgrades = UpgradeInventories.forMachine(is, 2, this::onUpgradesChanged); - } else { - System.out.println("[ExtendedAE_Plus] Applied Flux不存在,当前槽数异常: " + currentSlots + ",跳过修改"); - return; } } - - System.out.println("[ExtendedAE_Plus] InterfaceLogic升级槽扩展完成,最终槽数: " + this.upgrades.size()); } catch (Exception e) { // 发生异常时不修改升级槽,确保不会崩溃 - System.err.println("[ExtendedAE_Plus] Failed to expand interface upgrades: " + e.getMessage()); - e.printStackTrace(); } } } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicAdvancedMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicAdvancedMixin.java index 18e5a4e..31b4654 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicAdvancedMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicAdvancedMixin.java @@ -94,7 +94,6 @@ public class PatternProviderLogicAdvancedMixin implements AdvancedBlockingHolder @Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL")) private void onExportSettings(CompoundTag output, CallbackInfo ci) { - System.out.println(this.eap$advancedBlocking); output.putBoolean(EAP_ADV_BLOCKING_KEY, this.eap$advancedBlocking); } diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicDoublingMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicDoublingMixin.java index ff3152d..13806f2 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicDoublingMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/helpers/PatternProviderLogicDoublingMixin.java @@ -76,7 +76,6 @@ public class PatternProviderLogicDoublingMixin implements SmartDoublingHolder { @Inject(method = "exportSettings(Lnet/minecraft/nbt/CompoundTag;)V", at = @At("TAIL")) private void onExportSettings(CompoundTag output, CallbackInfo ci) { - System.out.println(this.eap$smartDoubling); output.putBoolean(EAP_SMART_DOUBLING_KEY, this.eap$smartDoubling); } From a3ae6b03350fbb5b32a8b4aa4c0cfbbaba6683cb Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Tue, 23 Sep 2025 19:30:42 +0800 Subject: [PATCH 30/34] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=90=88=E6=88=90?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 2 +- .../java/com/extendedae_plus/init/ModCreativeTabs.java | 3 +++ .../data/extendedae_plus/recipes/channel_card.json | 8 ++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/data/extendedae_plus/recipes/channel_card.json diff --git a/gradle.properties b/gradle.properties index ba9b599..d673cb8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs=-Xmx1G loom.platform = forge # Mod properties -mod_version = 1.4.3 +mod_version = 1.4.3-beta maven_group = com.extendedae_plus archives_name = extendedae_plus diff --git a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java index 293ac8e..75a95b7 100644 --- a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java +++ b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java @@ -37,6 +37,9 @@ public final class ModCreativeTabs { output.accept(ModItems.createEntitySpeedCardStack(16)); output.accept(ModItems.INFINITY_BIGINTEGER_CELL_ITEM.get()); + + // 频道卡 + output.accept(ModItems.CHANNEL_CARD.get()); }) .build()); } diff --git a/src/main/resources/data/extendedae_plus/recipes/channel_card.json b/src/main/resources/data/extendedae_plus/recipes/channel_card.json new file mode 100644 index 0000000..d6b23ce --- /dev/null +++ b/src/main/resources/data/extendedae_plus/recipes/channel_card.json @@ -0,0 +1,8 @@ +{ + "type": "minecraft:crafting_shapeless", + "ingredients": [ + { "item": "ae2:advanced_card" }, + { "item": "extendedae_plus:wireless_transceiver" } + ], + "result": { "item": "extendedae_plus:channel_card", "count": 1 } +} From 48a230386f75bc78e8bdca8b5451eba318667177 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Tue, 23 Sep 2025 19:41:01 +0800 Subject: [PATCH 31/34] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AE=9E=E4=BD=93?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/extendedae_plus/ExtendedAEPlus.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index 00cd4c1..037a65a 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -72,6 +72,14 @@ public class ExtendedAEPlus { ModNetwork.register(); // 注册自定义 Curios 宿主定位器,便于将菜单宿主信息在服务端与客户端间同步 MenuLocators.register(CuriosItemLocator.class, CuriosItemLocator::writeToPacket, CuriosItemLocator::readFromPacket); + + // 绑定方块实体类型,避免 blockEntityClass 为 null 的问题 + ModBlocks.ASSEMBLER_MATRIX_UPLOAD_CORE.get().setBlockEntity( + com.extendedae_plus.content.matrix.UploadCoreBlockEntity.class, + ModBlockEntities.UPLOAD_CORE_BE.get(), + null, + null + ); }); } From d3a47ea3d3562bb453d989304e5c0bcdea0bb4c9 Mon Sep 17 00:00:00 2001 From: GaLicn <133291877+GaLicn@users.noreply.github.com> Date: Tue, 23 Sep 2025 20:11:31 +0800 Subject: [PATCH 32/34] 1 --- build.gradle | 2 +- .../mixin/MixinConditions.java | 23 +++++++ .../mixin/ae2/CraftingCPUClusterMixin.java | 62 ++++++++----------- 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/build.gradle b/build.gradle index 25fe6a4..4b8c05f 100644 --- a/build.gradle +++ b/build.gradle @@ -106,7 +106,7 @@ dependencies { //mae2 -/* modRuntimeOnly "curse.maven:modern-ae2-additions-1028068:6827727"*/ + modRuntimeOnly "curse.maven:modern-ae2-additions-1028068:6827727" modCompileOnly "curse.maven:modern-ae2-additions-1028068:6827727" //aea diff --git a/src/main/java/com/extendedae_plus/mixin/MixinConditions.java b/src/main/java/com/extendedae_plus/mixin/MixinConditions.java index 7a47b91..595bf1c 100644 --- a/src/main/java/com/extendedae_plus/mixin/MixinConditions.java +++ b/src/main/java/com/extendedae_plus/mixin/MixinConditions.java @@ -64,6 +64,29 @@ public class MixinConditions implements IMixinConfigPlugin { return true; // 总是加载,在Mixin内部进行运行时检查 } + // 对于CraftingCPUClusterMixin,检查MAE2是否存在 + if (mixinClassName.contains("CraftingCPUClusterMixin")) { + try { + // 检查ModList是否已初始化 + if (net.minecraftforge.fml.ModList.get() == null) { + System.out.println("[ExtendedAE_Plus] ModList未初始化,默认应用CraftingCPU Mixin: " + mixinClassName); + return true; // 未初始化时默认应用 + } + + boolean mae2Exists = net.minecraftforge.fml.ModList.get().isLoaded("mae2"); + boolean shouldApply = !mae2Exists; + + System.out.println("[ExtendedAE_Plus] CraftingCPU Mixin检查: " + mixinClassName + + ", MAE2存在: " + mae2Exists + + ", 应用Mixin: " + shouldApply); + + return shouldApply; + } catch (Exception e) { + System.out.println("[ExtendedAE_Plus] ModList检查失败,默认跳过CraftingCPU Mixin: " + mixinClassName); + return false; // 出错时默认跳过,避免冲突 + } + } + // 其他Mixin正常应用 System.out.println("[ExtendedAE_Plus] 加载Mixin: " + mixinClassName); return true; diff --git a/src/main/java/com/extendedae_plus/mixin/ae2/CraftingCPUClusterMixin.java b/src/main/java/com/extendedae_plus/mixin/ae2/CraftingCPUClusterMixin.java index 7c76d90..ec1a5c8 100644 --- a/src/main/java/com/extendedae_plus/mixin/ae2/CraftingCPUClusterMixin.java +++ b/src/main/java/com/extendedae_plus/mixin/ae2/CraftingCPUClusterMixin.java @@ -1,48 +1,36 @@ package com.extendedae_plus.mixin.ae2; import appeng.blockentity.crafting.CraftingBlockEntity; -import appeng.blockentity.crafting.CraftingMonitorBlockEntity; import appeng.me.cluster.implementations.CraftingCPUCluster; -import appeng.me.helpers.MachineSource; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; -import org.spongepowered.asm.mixin.Shadow; - -import java.util.List; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; +import org.spongepowered.asm.mixin.injection.Redirect; @Mixin(value = CraftingCPUCluster.class, remap = false, priority = 2000) public abstract class CraftingCPUClusterMixin { - - @Shadow private List blockEntities; - @Shadow private List status; - @Shadow private MachineSource machineSrc; - @Shadow private long storage; - @Shadow private int accelerator; - - /** - * 完全重写addBlockEntity方法,移除16线程的硬限制 - * @author ExtendedAE_Plus - * @reason 移除单方块16线程的硬限制,允许更高的线程数 - */ - @Overwrite - void addBlockEntity(CraftingBlockEntity te) { - if (this.machineSrc == null || te.isCoreBlock()) { - this.machineSrc = new MachineSource(te); - } + // 1) 提升“单方块线程上限”的常量,避免抛出 IAE 的 IllegalArgumentException + @ModifyConstant( + method = "addBlockEntity(Lappeng/blockentity/crafting/CraftingBlockEntity;)V", + constant = @Constant(intValue = 16) + ) + private int extendedae_plus$raisePerUnitLimit(int original) { + // 放宽到极大值,完全取消单方块 16 线程的硬限制 + return Integer.MAX_VALUE; + } - te.setCoreBlock(false); - te.saveChanges(); - this.blockEntities.add(0, te); - - if (te instanceof CraftingMonitorBlockEntity) { - this.status.add((CraftingMonitorBlockEntity) te); - } - if (te.getStorageBytes() > 0) { - this.storage += te.getStorageBytes(); - } - if (te.getAcceleratorThreads() > 0) { - // 移除原来的16线程限制,直接添加线程数 - this.accelerator += te.getAcceleratorThreads(); - } + // 2) 保持统计使用原始线程值(若存在多处调用),不再返回固定 16 + @Redirect( + method = "addBlockEntity(Lappeng/blockentity/crafting/CraftingBlockEntity;)V", + at = @At( + value = "INVOKE", + target = "Lappeng/blockentity/crafting/CraftingBlockEntity;getAcceleratorThreads()I", + ordinal = 1 + ) + ) + private int extendedae_plus$onGetThreadsForLimitCheck(CraftingBlockEntity te) { + // 返回原始线程数,确保总并行单元不被错误下限 + return te.getAcceleratorThreads(); } } From dbf2ad7be24560c153096e93fb308427371a16b2 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Tue, 23 Sep 2025 23:00:58 +0800 Subject: [PATCH 33/34] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=AE=9E?= =?UTF-8?q?=E4=BD=93=E5=8A=A0=E9=80=9F=E5=99=A8=E9=BB=91=E5=90=8D=E5=8D=95?= =?UTF-8?q?=E5=92=8C=E5=80=8D=E7=8E=87=E5=90=8D=E5=8D=95=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E7=83=AD=E9=87=8D=E8=BD=BD=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/ConfigParsingUtils.java | 66 +++++++++++++++---- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java b/src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java index b9377a5..6323978 100644 --- a/src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java +++ b/src/main/java/com/extendedae_plus/util/ConfigParsingUtils.java @@ -1,6 +1,7 @@ package com.extendedae_plus.util; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; @@ -114,34 +115,71 @@ public final class ConfigParsingUtils { // ------------------ 全局缓存与接口 ------------------ private static volatile List cachedBlacklist = null; private static volatile List cachedMultiplierEntries = null; + private static volatile List cachedBlacklistSourceSnapshot = null; + private static volatile List cachedMultiplierSourceSnapshot = null; private static final Object CACHE_LOCK = new Object(); /** * 获取已解析并缓存的黑名单(线程安全、懒加载)。 */ public static List getCachedBlacklist(java.util.List source) { - if (cachedBlacklist == null) { - synchronized (CACHE_LOCK) { - if (cachedBlacklist == null) { - cachedBlacklist = compilePatterns(source); - } - } + List normalized = normalizeSource(source); + + // fast path: identical snapshot reference or equal contents + if (cachedBlacklist != null && listEquals(cachedBlacklistSourceSnapshot, normalized)) { + return Collections.unmodifiableList(cachedBlacklist); + } + + synchronized (CACHE_LOCK) { + if (cachedBlacklist == null || !listEquals(cachedBlacklistSourceSnapshot, normalized)) { + cachedBlacklist = compilePatterns(normalized); + cachedBlacklistSourceSnapshot = normalized.isEmpty() ? Collections.emptyList() : new ArrayList<>(normalized); + } + return Collections.unmodifiableList(cachedBlacklist); } - return java.util.List.copyOf(cachedBlacklist); } /** * 获取已解析并缓存的倍率列表(线程安全、懒加载)。 */ public static List getCachedMultiplierEntries(java.util.List source) { - if (cachedMultiplierEntries == null) { - synchronized (CACHE_LOCK) { - if (cachedMultiplierEntries == null) { - cachedMultiplierEntries = parseMultiplierList(source); - } - } + List normalized = normalizeSource(source); + + if (cachedMultiplierEntries != null && listEquals(cachedMultiplierSourceSnapshot, normalized)) { + return Collections.unmodifiableList(cachedMultiplierEntries); } - return java.util.List.copyOf(cachedMultiplierEntries); + + synchronized (CACHE_LOCK) { + if (cachedMultiplierEntries == null || !listEquals(cachedMultiplierSourceSnapshot, normalized)) { + cachedMultiplierEntries = parseMultiplierList(normalized); + cachedMultiplierSourceSnapshot = normalized.isEmpty() ? Collections.emptyList() : new ArrayList<>(normalized); + } + return Collections.unmodifiableList(cachedMultiplierEntries); + } + } + + // Normalize the incoming source list: trim entries, drop blanks, keep stable ordering + private static List normalizeSource(java.util.List source) { + List out = new ArrayList<>(); + if (source == null) return out; + for (String s : source) { + if (s == null) continue; + String t = s.trim(); + if (t.isEmpty()) continue; + out.add(t); + } + return out; + } + + // Null-safe equality for two lists of strings. Uses size + element equals. + private static boolean listEquals(List a, List b) { + if (a == b) return true; + if (a == null || b == null) return false; + if (a.size() != b.size()) return false; + for (int i = 0; i < a.size(); i++) { + if (!a.get(i).equals(b.get(i))) return false; + } + return true; } /** From 8c3af60eb19fd8618f733b474579c46c9a24113d Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Tue, 23 Sep 2025 23:23:08 +0800 Subject: [PATCH 34/34] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AE=9E?= =?UTF-8?q?=E4=BD=93=E5=8A=A0=E9=80=9F=E5=99=A8=E8=83=BD=E9=87=8F=E4=B8=8D?= =?UTF-8?q?=E8=B6=B3=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ae/menu/EntitySpeedTickerMenu.java | 10 +++++++ .../ae/parts/EntitySpeedTickerPart.java | 30 +++++++++++++++++-- .../ae/screen/EntitySpeedTickerScreen.java | 5 +++- .../ae2/screens/entity_speed_ticker.json | 3 +- .../assets/extendedae_plus/lang/en_us.json | 1 + .../assets/extendedae_plus/lang/zh_cn.json | 1 + 6 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/extendedae_plus/ae/menu/EntitySpeedTickerMenu.java b/src/main/java/com/extendedae_plus/ae/menu/EntitySpeedTickerMenu.java index fcc6e5d..8c09948 100644 --- a/src/main/java/com/extendedae_plus/ae/menu/EntitySpeedTickerMenu.java +++ b/src/main/java/com/extendedae_plus/ae/menu/EntitySpeedTickerMenu.java @@ -30,6 +30,8 @@ public class EntitySpeedTickerMenu extends UpgradeableMenu extends Up int energyCardCount = getMenu().energyCardCount; double multiplier = getMenu().multiplier; int effectiveSpeed = getMenu().effectiveSpeed; - double finalPower = PowerUtils.computeFinalPowerForProduct(effectiveSpeed, energyCardCount); double remainingRatio = PowerUtils.getRemainingRatio(energyCardCount); + // 如果网络能量不足,优先显示警告信息并在能量值处显示 0 + if (getMenu().networkEnergyInsufficient) { + setTextContent("enable", Component.translatable("screen.extendedae_plus.entity_speed_ticker.warning_network_energy_insufficient")); + } setTextContent("speed", Component.translatable("screen.extendedae_plus.entity_speed_ticker.speed", effectiveSpeed)); setTextContent("energy", Component.translatable("screen.extendedae_plus.entity_speed_ticker.energy", Platform.formatPower(finalPower, false))); setTextContent("power_ratio", Component.translatable("screen.extendedae_plus.entity_speed_ticker.power_ratio", PowerUtils.formatPercentage(remainingRatio))); diff --git a/src/main/resources/assets/ae2/screens/entity_speed_ticker.json b/src/main/resources/assets/ae2/screens/entity_speed_ticker.json index e30da1a..f8156f6 100644 --- a/src/main/resources/assets/ae2/screens/entity_speed_ticker.json +++ b/src/main/resources/assets/ae2/screens/entity_speed_ticker.json @@ -50,8 +50,7 @@ "left": 88 }, "align": "CENTER" - } - , + }, "multiplier": { "position": { "top": 80, diff --git a/src/main/resources/assets/extendedae_plus/lang/en_us.json b/src/main/resources/assets/extendedae_plus/lang/en_us.json index 07156e3..9d88bc6 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -54,6 +54,7 @@ "screen.extendedae_plus.entity_speed_ticker.power_ratio": "Power ratio: %s", "screen.extendedae_plus.entity_speed_ticker.speed": "Current speed multiplier: %d", "screen.extendedae_plus.entity_speed_ticker.multiplier": "Extra consumption multiplier: %s", + "screen.extendedae_plus.entity_speed_ticker.warning_network_energy_insufficient": "§c§lInsufficient network energy", "item.extendedae_plus.entity_speed_ticker.tip.requirement": "Requires Entity Acceleration Card(s) to enable acceleration", "item.extendedae_plus.entity_speed_ticker.tip.max": "Maximum up to 1024x speed", diff --git a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json index a245edb..eb60ec2 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -54,6 +54,7 @@ "screen.extendedae_plus.entity_speed_ticker.power_ratio": "功耗比例: %s", "screen.extendedae_plus.entity_speed_ticker.speed": "当前加速倍率: %d", "screen.extendedae_plus.entity_speed_ticker.multiplier": "额外消耗倍率: %s", + "screen.extendedae_plus.entity_speed_ticker.warning_network_energy_insufficient": "§c§l网络能量不足", "item.extendedae_plus.entity_speed_ticker.tip.requirement": "需要放入实体加速卡以启用加速", "item.extendedae_plus.entity_speed_ticker.tip.max": "最高可达 1024x 加速",