From 0f00c49d8a0385e6ceeb84b447fed8d86ebad1a8 Mon Sep 17 00:00:00 2001 From: C-H716 <1536152356@qq.com> Date: Thu, 18 Sep 2025 14:47:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A7=BB=E6=A4=8D1.20=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E6=97=A0=E9=99=90=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 --- .../com/extendedae_plus/ExtendedAEPlus.java | 67 +-- .../InfinityBigIntegerCellHandler.java | 36 ++ .../InfinityBigIntegerCellInventory.java | 435 ++++++++++++++++++ .../ae/items/InfinityBigIntegerCellItem.java | 81 ++++ .../extendedae_plus/init/ModCreativeTabs.java | 2 + .../com/extendedae_plus/init/ModItems.java | 6 + .../util/storage/InfinityDataStorage.java | 61 +++ .../util/storage/InfinityStorageManager.java | 166 +++++++ .../assets/extendedae_plus/lang/en_us.json | 7 +- .../assets/extendedae_plus/lang/zh_cn.json | 3 + .../models/item/infinity_biginteger_cell.json | 8 + .../item/infinity_biginteger_cell.png | Bin 0 -> 2266 bytes .../recipe/infinity_biginteger_cell.json | 18 + 13 files changed, 855 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java create mode 100644 src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java create mode 100644 src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java create mode 100644 src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java create mode 100644 src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java create mode 100644 src/main/resources/assets/extendedae_plus/models/item/infinity_biginteger_cell.json create mode 100644 src/main/resources/assets/extendedae_plus/textures/item/infinity_biginteger_cell.png create mode 100644 src/main/resources/data/extendedae_plus/recipe/infinity_biginteger_cell.json diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java index aaf641c..75c26a7 100644 --- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java +++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java @@ -1,38 +1,29 @@ package com.extendedae_plus; -import org.slf4j.Logger; - -import com.mojang.logging.LogUtils; -import com.extendedae_plus.config.ModConfigs; -import com.extendedae_plus.init.ModBlocks; -import com.extendedae_plus.init.ModBlockEntities; -import com.extendedae_plus.init.ModCreativeTabs; -import com.extendedae_plus.init.ModItems; -import com.extendedae_plus.init.ModMenuTypes; -import com.extendedae_plus.init.ModCapabilities; -import com.extendedae_plus.network.ModNetwork; - -import net.minecraft.resources.ResourceLocation; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.core.registries.Registries; -import net.minecraft.network.chat.Component; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.state.BlockBehaviour; -import net.minecraft.world.level.material.MapColor; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.bus.api.IEventBus; -import net.neoforged.bus.api.SubscribeEvent; -import net.neoforged.fml.common.Mod; -import net.neoforged.fml.config.ModConfig; -import net.neoforged.fml.ModContainer; -import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; -import net.neoforged.neoforge.common.NeoForge; -import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; -import net.neoforged.neoforge.event.server.ServerStartingEvent; -import net.neoforged.neoforge.registries.DeferredRegister; +import appeng.api.storage.StorageCells; import appeng.block.AEBaseEntityBlock; import appeng.blockentity.crafting.CraftingBlockEntity; -import appeng.core.definitions.AEBlockEntities; +import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler; +import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellInventory; +import com.extendedae_plus.config.ModConfigs; +import com.extendedae_plus.init.*; +import com.extendedae_plus.network.ModNetwork; +import com.extendedae_plus.util.storage.InfinityStorageManager; +import com.mojang.logging.LogUtils; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Blocks; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.config.ModConfig; +import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.level.LevelEvent; +import net.neoforged.neoforge.event.server.ServerStartingEvent; +import org.slf4j.Logger; // The value here should match an entry in the META-INF/neoforge.mods.toml file @Mod(ExtendedAEPlus.MODID) @@ -65,8 +56,10 @@ public class ExtendedAEPlus { // Note that this is necessary if and only if we want *this* class (ExtendedAEPlus) to respond directly to events. // Do not add this line if there are no @SubscribeEvent-annotated functions in this class, like onServerStarting() below. NeoForge.EVENT_BUS.register(this); + NeoForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad); - // 由 ModCreativeTabs#MAIN 的 displayItems 负责填充创造物品栏,无需额外事件注入 + NeoForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick); + NeoForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerStopping); // 注册配置:接入自定义的 ModConfigs modContainer.registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC); @@ -82,7 +75,7 @@ public class ExtendedAEPlus { LOGGER.info("HELLO FROM COMMON SETUP"); // 示例日志,避免引用不存在的模板 Config 字段 LOGGER.info("DIRT BLOCK >> {}", BuiltInRegistries.BLOCK.getKey(Blocks.DIRT)); - + StorageCells.addCellHandler(InfinityBigIntegerCellHandler.INSTANCE); // 绑定 AE2 的 CraftingBlockEntity 到本模组的自定义加速器方块,避免 AEBaseEntityBlock.blockEntityType 为空 event.enqueueWork(() -> { try { @@ -115,5 +108,13 @@ public class ExtendedAEPlus { // Do something when the server starts LOGGER.info("HELLO from server starting"); } + + + // 在世界加载时注册/加载 SavedData + private static void onLevelLoad(LevelEvent.Load event) { + if (event.getLevel() instanceof ServerLevel serverLevel) { + InfinityStorageManager.getForLevel(serverLevel); + } + } } 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 new file mode 100644 index 0000000..bc7c29c --- /dev/null +++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java @@ -0,0 +1,36 @@ +package com.extendedae_plus.ae.api.storage; + +import appeng.api.storage.cells.ICellHandler; +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); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..b7e3e14 --- /dev/null +++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java @@ -0,0 +1,435 @@ +package com.extendedae_plus.ae.api.storage; + +import appeng.api.config.Actionable; +import appeng.api.networking.security.IActionSource; +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.KeyCounter; +import appeng.api.storage.cells.CellState; +import appeng.api.storage.cells.ISaveProvider; +import appeng.api.storage.cells.StorageCell; +import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem; +import com.extendedae_plus.util.storage.InfinityDataStorage; +import com.extendedae_plus.util.storage.InfinityStorageManager; +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.network.chat.Component; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.CustomData; +import net.neoforged.neoforge.event.server.ServerStoppingEvent; +import net.neoforged.neoforge.event.tick.ServerTickEvent; + +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; + +/** + * InfinityBigIntegerCellInventory + *

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

+ * 重要字段: + * - stack: 关联的 ItemStack,NB T 中保存 UUID 与缓存信息 + * - container: AE2 提供的保存回调 (ISaveProvider),用于合并与触发持久化 + * - storedMap: 延迟初始化的内存映射,减少未使用时内存占用 + * - totalStored: 缓存的总数量 (BigInteger),避免频繁全表扫描 + * - isPersisted: 标记内存状态是否已同步到持久层 + */ +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; + // AE2 提供的保存提供者,用于在容器中批量保存时触发回调 + private final ISaveProvider container; + // 内存中的键-数量映射(使用 BigInteger 支持超长数量,延迟初始化) + private Object2ObjectMap storedMap = null; + // 标记是否已持久化到 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; + } + + // 服务器 tick 回调:合并并执行待持久化项 + public static void onServerTick(ServerTickEvent.Post event) { + // NeoForge 提供 Pre/Post 子类以区分阶段,Post 对应原来的 Phase.END + InfinityBigIntegerCellInventory inv; + // 处理本次 tick 中的全部待持久化项 + while ((inv = PENDING_PERSIST.poll()) != null) { + try { + if (!inv.isPersisted) { + inv.persist(); + } + } catch (Throwable ignored) { + // 忽略单项错误,继续处理其余队列 + } + } + } + + // 在服务器停止时被调用,立即强制持久化队列中的所有实例 + public static void onServerStopping(ServerStoppingEvent event) { + InfinityBigIntegerCellInventory inv; + while ((inv = PENDING_PERSIST.poll()) != null) { + try { + if (!inv.isPersisted) { + inv.persist(); + } + } catch (Throwable ignored) { + // 忽略单项错误,继续尝试持久化其它实例 + } + } + // 额外尝试将全局存储管理器标记为脏以确保 SavedData 被写回(在单人模式下可能直接由系统触发) + try { + var stor = getStorageInstance(); + if (stor != null) stor.setDirty(); + } catch (Throwable ignored) { + } + } + + // 将 BigInteger 格式化为带单位的字符串,保留两位小数 + public static String formatBigInteger(BigInteger number) { + // 使用局部 DF(非线程安全),但 Minecraft 通常在主线程运行 + BigDecimal bd = new BigDecimal(number); + BigDecimal thousand = new BigDecimal(1000); + String[] units = new String[]{"", "K", "M", "G", "T", "P", "E", "Z", "Y"}; + int idx = 0; + while (bd.compareTo(thousand) >= 0 && idx < units.length - 1) { + bd = bd.divide(thousand, 2, RoundingMode.HALF_UP); + idx++; + } + if (idx == 0) { + return bd.setScale(0, RoundingMode.DOWN).toPlainString(); + } + return DF.format(bd.doubleValue()) + units[idx]; + } + + // 获取当前存储单元的数据存储对象 + private InfinityDataStorage getCellStorage() { + if (this.getUUID() == null) { + // 如果没有UUID,返回空存储 + return InfinityDataStorage.EMPTY; + } else { + // 否则获取或创建对应UUID的存储 + return getStorageInstance().getOrCreateCell(getUUID()); + } + } + + // 获取存储单元状态(空/非空) + @Override + public CellState getStatus() { + if (this.getCellStoredMap().isEmpty()) { + return CellState.EMPTY; + } + return CellState.NOT_EMPTY; + } + + // 获取存储单元的待机能耗 + @Override + public double getIdleDrain() { + return 512; + } + + // 获取存储单元的描述(此处返回null,可自定义) + @Override + public Component getDescription() { + return null; + } + + // 判断物品堆栈是否有UUID + public boolean hasUUID() { + CustomData customData = stack.get(DataComponents.CUSTOM_DATA); + if (customData == null) return false; + CompoundTag tag = customData.copyTag(); + return tag != null && tag.contains("uuid"); + } + + // 获取物品堆栈的UUID + public UUID getUUID() { + CustomData customData = stack.get(DataComponents.CUSTOM_DATA); + if (customData == null) return null; + CompoundTag tag = customData.copyTag(); + if (tag != null && tag.contains("uuid")) { + return tag.getUUID("uuid"); + } + return null; + } + + // 获取或初始化存储映射 + private Object2ObjectMap getCellStoredMap() { + if (storedMap == null) { + storedMap = new Object2ObjectOpenHashMap<>(); + this.loadCellStoredMap(); + } + return storedMap; + } + + // 从存储中加载物品映射 + private void loadCellStoredMap() { + boolean corruptedTag = false; // 标记数据是否损坏 + if (!this.hasUUID()) return; + + ListTag keys = this.getCellStorage().keys; + ListTag amounts = this.getCellStorage().amounts; + + int len = Math.min(keys.size(), amounts.size()); + + HolderLookup.Provider registries = getStorageInstance().getRegistries(); + + for (int i = 0; i < len; i++) { + AEKey key = AEKey.fromTagGeneric(registries,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); + } + } + } + + // 获取所有可用的物品堆栈及其数量 + @Override + public void getAvailableStacks(KeyCounter out) { + BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE); + Object2ObjectMap map = getCellStoredMap(); + for (Object2ObjectMap.Entry entry : map.object2ObjectEntrySet()) { + AEKey key = entry.getKey(); + BigInteger value = entry.getValue(); + + // 当前 KeyCounter 中已有的值(long) + long existing = out.get(key); + + // 将 existing 与当前 value 做 BigInteger 累加并饱和到 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) + if (existing == Long.MAX_VALUE) { + // 已经饱和,无需再添加 + continue; + } + long delta; + if (toSet == Long.MAX_VALUE) { + delta = Long.MAX_VALUE - existing; + } else { + delta = toSet - existing; + } + if (delta != 0) { + out.add(key, delta); + } + } + } + + // 持久化存储单元数据到全局存储 + @Override + public void persist() { + if (this.isPersisted) + return; + Object2ObjectMap map = this.getCellStoredMap(); + if (map.isEmpty()) { + // 如果存储为空,移除UUID和全局存储中的数据 + if (this.hasUUID()) { + getStorageInstance().removeCell(getUUID()); + // 从 CustomData 中移除缓存字段 + try { + CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> { + tag.remove("uuid"); + tag.remove("total"); + }); + } catch (Throwable ignored) { + } + } + return; + } + // 构建要保存的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(getStorageInstance().getRegistries())); + 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()) { + getStorageInstance().updateCell(this.getUUID(), new InfinityDataStorage()); + } else { + // amounts 现在为 CompoundTag 列表 + getStorageInstance().modifyCell(this.getUUID(), keys, amountTags); + } + // 将缓存的 totalStored 同步到 ItemStack 的 CustomData(优先使用 long) + try { + CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> { + if (totalStored.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) { + tag.putLong("total", totalStored.longValue()); + } else { + tag.putString("total", totalStored.toString()); + } + // 将当前已存储的不同物品种类数缓存到 CustomData(键名: "types"),用于客户端 tooltip 显示 + int typesCount = this.getCellStoredMap().size(); + tag.putInt("types", typesCount); + }); + } catch (Throwable ignored) { + } + isPersisted = true; + } + + // 插入物品到存储单元 + @Override + public long insert(AEKey what, long amount, Actionable mode, IActionSource source) { + // 数量为0或类型不匹配直接返回 + if (amount == 0) + return 0; + // 不允许存储无限单元自身 + if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) + return 0; + // 如果没有UUID,生成UUID并初始化存储 + if (!this.hasUUID()) { + UUID id = UUID.randomUUID(); + try { + CustomData.update(DataComponents.CUSTOM_DATA, stack, tag -> tag.putUUID("uuid", id)); + } catch (Throwable ignored) { + } + getStorageInstance().getOrCreateCell(getUUID()); + // 确保 storedMap 初始化并从持久层加载数据 + this.getCellStoredMap(); + } + Object2ObjectMap map = this.getCellStoredMap(); + BigInteger currentAmount = map.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)); + this.saveChanges(); + } + return amount; + } + + // 从存储单元提取物品 + @Override + public long extract(AEKey what, long amount, Actionable mode, IActionSource source) { + Object2ObjectMap map = this.getCellStoredMap(); + BigInteger currentAmount = map.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 (mode == Actionable.MODULATE) { + map.remove(what); + // 更新 cached total + // 如果 currentAmount 大于 Long.MAX_VALUE,totalStored 减去 currentAmount 会保留大整数 + totalStored = totalStored.subtract(currentAmount); + this.saveChanges(); + } + return ret; + } else { + // 提取部分 + if (mode == Actionable.MODULATE) { + map.put(what, currentAmount.subtract(requested)); + // 更新 cached total + totalStored = totalStored.subtract(requested); + this.saveChanges(); + } + return amount; + } + } + 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 new file mode 100644 index 0000000..7002ba9 --- /dev/null +++ b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java @@ -0,0 +1,81 @@ +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.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.LongTag; +import net.minecraft.nbt.Tag; +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.item.component.CustomData; +import org.jetbrains.annotations.NotNull; + +import java.math.BigInteger; +import java.util.List; + +public class InfinityBigIntegerCellItem extends Item { + + public InfinityBigIntegerCellItem() { + super(new Properties().stacksTo(1).fireResistant()); + } + + @Override + public void appendHoverText(ItemStack stack, + @NotNull TooltipContext context, + List tooltip, + @NotNull TooltipFlag tooltipFlag) { + 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); + CustomData customData = stack.get(DataComponents.CUSTOM_DATA); + if (customData != null) { + CompoundTag tag = customData.copyTag(); + + 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)) + ); + + if (tag.contains("types")) { + try { + int types = tag.getInt("types"); + tooltip.add( + Component.literal("Types: ") + .withStyle(ChatFormatting.GRAY) + .append(Component.literal(String.valueOf(types)).withStyle(ChatFormatting.GREEN)) + ); + } catch (Exception ignored) { + } + } + + if (tag.contains("total")) { + BigInteger total = BigInteger.ZERO; + Tag t = tag.get("total"); + try { + if (t instanceof LongTag) { + total = BigInteger.valueOf(tag.getLong("total")); + } else { + String s = tag.getString("total"); + total = new BigInteger(s); + } + } catch (Exception ignored) { + } + 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/init/ModCreativeTabs.java b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java index 8a1ed50..2846159 100644 --- a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java +++ b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java @@ -26,6 +26,8 @@ public final class ModCreativeTabs { output.accept(ModItems.ACCELERATOR_64x.get()); output.accept(ModItems.ACCELERATOR_256x.get()); output.accept(ModItems.ACCELERATOR_1024x.get()); + + output.accept(ModItems.INFINITY_BIGINTEGER_CELL_ITEM.get()); }) .build()); } diff --git a/src/main/java/com/extendedae_plus/init/ModItems.java b/src/main/java/com/extendedae_plus/init/ModItems.java index f0926b4..113ec98 100644 --- a/src/main/java/com/extendedae_plus/init/ModItems.java +++ b/src/main/java/com/extendedae_plus/init/ModItems.java @@ -1,6 +1,7 @@ package com.extendedae_plus.init; import com.extendedae_plus.ExtendedAEPlus; +import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.Item; import net.neoforged.neoforge.registries.DeferredItem; @@ -46,4 +47,9 @@ public final class ModItems { "1024x_crafting_accelerator", () -> new BlockItem(ModBlocks.ACCELERATOR_1024x.get(), new Item.Properties()) ); + + public static final DeferredItem INFINITY_BIGINTEGER_CELL_ITEM = ITEMS.register( + "infinity_biginteger_cell", InfinityBigIntegerCellItem::new + ); + } diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java new file mode 100644 index 0000000..4ef9036 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java @@ -0,0 +1,61 @@ +package com.extendedae_plus.util.storage; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; + +/** + * InfinityDataStorage + * + * 表示单个 UUID 对应的持久化数据容器,直接映射到世界存档中的一项记录。 + * 数据结构说明: + * - keys: 存放序列化后的 AEKey(每项为 CompoundTag),用于标识不同的存储条目 + * - amounts: 与 keys 一一对应的数量列表(每项为 CompoundTag),采用混合表示: + * - 当数量能放入 long 时,CompoundTag 包含键 "l" 存放 long 值 + * - 当数量超出 long 时,CompoundTag 包含键 "s" 存放 BigInteger 的字符串形式 + * + * 该类提供将内存数据与 NBT 之间互转的辅助方法,供 `SavedData` 在世界保存/加载时调用。 + */ +public class InfinityDataStorage { + + /** 空实例(表示没有数据) */ + public static final InfinityDataStorage EMPTY = new InfinityDataStorage(); + + /** 序列化的键列表(NBT ListTag,元素为 CompoundTag) */ + public ListTag keys; + /** + * 与 keys 对应的数量列表(NBT ListTag,元素为 CompoundTag): + * - 若数量能放入 long,则 CompoundTag 包含键 "l"(long) + * - 否则包含键 "s"(String) 存放 BigInteger 的字符串形式 + */ + public ListTag amounts; + + public InfinityDataStorage() { + this(new ListTag(), new ListTag()); + } + + private InfinityDataStorage(ListTag keys, ListTag amounts) { + this.keys = keys; + this.amounts = amounts; + } + + /** + * 将当前数据封装为 CompoundTag 以写入存档 + */ + public CompoundTag serializeNBT() { + CompoundTag nbt = new CompoundTag(); + nbt.put("keys", keys); + nbt.put("amounts", amounts); + return nbt; + } + + /** + * 从存档读取数据并构造实例 + */ + 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); + } +} diff --git a/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java new file mode 100644 index 0000000..ea48942 --- /dev/null +++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java @@ -0,0 +1,166 @@ +package com.extendedae_plus.util.storage; + +import net.minecraft.core.HolderLookup; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.saveddata.SavedData; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * InfinityStorageManager + *

+ * 世界级别的持久化容器,集中管理所有 InfinityBigInteger 存储单元的序列化数据。 + * 功能要点: + * - 在世界加载时从存档恢复所有 cell 的数据 + * - 提供按 UUID 获取/创建单个 cell 的数据容器 + * - 在世界保存时将内存数据打包为 NBT 写回存档 + */ +public class InfinityStorageManager extends SavedData { + private static final Factory FACTORY = new Factory<>(InfinityStorageManager::new, InfinityStorageManager::readNbt); + + /** + * SavedData 文件名常量 + */ + public static final String FILE_NAME = "eap_infinity_biginteger_cells"; + /** + * 全局单例实例(在世界加载时由 InfiniteBigIntegerStorageCell.onLevelLoad 填充) + */ + public static InfinityStorageManager INSTANCE = null; + /** + * UUID -> 数据 的内存映射 + */ + private final Map cells; + + @Nullable + private WeakReference registries; + + public InfinityStorageManager() { + this.cells = new HashMap<>(); + setDirty(); + } + + public InfinityStorageManager(Map cells) { + this.cells = cells; + setDirty(); + } + + /** + * 从 NBT 构造:用于在世界加载时从存档恢复数据 + */ + public InfinityStorageManager(CompoundTag nbt) { + this.cells = new HashMap<>(); + 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"))); + } + setDirty(); + } + + /** + * 根据给定的 ServerLevel 获取或创建该世界对应的 SavedData 实例并缓存到 INSTANCE + */ + public static InfinityStorageManager getForLevel(ServerLevel level) { + if (INSTANCE == null && level != null) { + INSTANCE = level.getDataStorage().computeIfAbsent(FACTORY, FILE_NAME); + INSTANCE.registries = new WeakReference<>(level.registryAccess()); + } + return INSTANCE; + } + + public HolderLookup.Provider getRegistries() { + var r = this.registries; + if (r == null) { + throw new IllegalStateException("StorageManager was not initialized properly."); + } + + var registries = r.get(); + if (registries == null) { + throw new IllegalStateException("Using a StorageManager whose server was already closed"); + } + + return registries; + } + + @Override + public @NotNull CompoundTag save(@NotNull CompoundTag nbt, HolderLookup.@NotNull Provider registries) { + // 将内存中的所有 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(); + } + + /** + * 获取或创建某个 UUID 对应的数据容器 + */ + public InfinityDataStorage getOrCreateCell(UUID uuid) { + if (!cells.containsKey(uuid)) { + updateCell(uuid, new InfinityDataStorage()); + } + 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; + } + updateCell(cellID, cellToModify); + } + + /** + * 删除某个 UUID 的持久化记录并标记为脏 + */ + public void removeCell(UUID uuid) { + cells.remove(uuid); + setDirty(); + } + + public static InfinityStorageManager readNbt(CompoundTag nbt, HolderLookup.Provider registries) { + Map cell = new HashMap<>(); + ListTag diskList = nbt.getList("list", CompoundTag.TAG_COMPOUND); + for (int i = 0; i < diskList.size(); i++) { + CompoundTag disk = diskList.getCompound(i); + cell.put(disk.getUUID("uuid"), InfinityDataStorage.loadFromNBT(disk.getCompound("data"))); + } + return new InfinityStorageManager(cell); + } + + public static InfinityStorageManager getInstance(MinecraftServer server) { + ServerLevel world = server.getLevel(ServerLevel.OVERWORLD); + InfinityStorageManager manager = null; + if (world != null) { + manager = world.getDataStorage().computeIfAbsent(FACTORY, FILE_NAME); + } + if (manager != null) { + manager.registries = new WeakReference<>(server.registryAccess()); + } + return manager; + } +} 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 a1f57b0..4072712 100644 --- a/src/main/resources/assets/extendedae_plus/lang/en_us.json +++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json @@ -24,8 +24,11 @@ "config.jade.plugin_extendedae_plus.wt_network_usable": "Show Network Online State", "extendedae_plus.tooltip.frequency": "Frequency: %d", "extendedae_plus.tooltip.master_mode": "Mode: %s", - "extendedae_plus.tooltip.locked": "Locked: %s" - , + "extendedae_plus.tooltip.locked": "Locked: %s", + "item.extendedae_plus.infinity_biginteger_cell": "§4De§cvou§6rer §eof §aCo§bsmic §dSilence", + "tooltip.extendedae_plus.infinity_biginteger_cell.summon1": "§6Through ninefold sacrifice, the Void echoes§r—§8Iava, Lord of the Void§r, bestows upon thee this artifact", + "tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b—§4A §dUni§cverse §eWith§ain §6A §bSin§5gle §9Point", + "screen.extendedae_plus.title": "ExtendedAE Plus Config", "config.extendedae_plus": "ExtendedAE Plus", "extendedae_plus.configuration.general": "General", 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 b176c1f..2fa9a07 100644 --- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json +++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json @@ -28,6 +28,9 @@ "item.extendedae_plus.64x_crafting_accelerator": "64x并行处理单元", "item.extendedae_plus.256x_crafting_accelerator": "256x并行处理单元", "item.extendedae_plus.1024x_crafting_accelerator": "1024x并行处理单元", + "item.extendedae_plus.infinity_biginteger_cell": "§4吞§c噬§6万§e籁§a的§b寂§d静", + "tooltip.extendedae_plus.infinity_biginteger_cell.summon1": "§6九重献祭, 终得虚空回响§r——觐见§8虚空之主Iava§r, 赐汝此物", + "tooltip.extendedae_plus.infinity_biginteger_cell.summon2": "§b——§4方§d寸§c之§e间§a, §6自§b有§5千§9寰", "config.jade.plugin_extendedae_plus.wireless_transceiver_info": "无线收发器信息", "config.jade.plugin_extendedae_plus.wt_frequency": "显示频率", diff --git a/src/main/resources/assets/extendedae_plus/models/item/infinity_biginteger_cell.json b/src/main/resources/assets/extendedae_plus/models/item/infinity_biginteger_cell.json new file mode 100644 index 0000000..235c138 --- /dev/null +++ b/src/main/resources/assets/extendedae_plus/models/item/infinity_biginteger_cell.json @@ -0,0 +1,8 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "extendedae_plus:item/infinity_biginteger_cell" + } +} + + diff --git a/src/main/resources/assets/extendedae_plus/textures/item/infinity_biginteger_cell.png b/src/main/resources/assets/extendedae_plus/textures/item/infinity_biginteger_cell.png new file mode 100644 index 0000000000000000000000000000000000000000..5c47ca4fdedf33c73a8afa63e5c1fe41f4978db7 GIT binary patch literal 2266 zcmcgueNYr-99|d)5hWog8wPb}Q}ctpyWQKp+uJx1Iq)zJ5GcSgb8q)O?)2_<-Cf`y zrqGNeq%2eOqiIT;v`;ll#}dgAF`Ud8ok%OgR2-eeufnvbcaP)320z9>x|zLw_uc1x zp6BJhA1G3_uoqzvHp z*sEnwSe;wMS1;f#0+u!vO%1S+zz0+g4fwo%g$-D-ATJBY+O!@+gAjFr6-(Cy(Go{K znjy*nwde?(HyB9NY|)t+r`cp=Qc%)B(s~1_CkULdu!Mm%Flgw9K{;7)u?4oQkQltO zVs2HHSiQcovQk%R)QNJLp0HRf8V5<@5P>UIew7R0e&ykQ1{+X#*(0eQ(T{43oKq}U ztr)c07lTg<)B2TAo?yoG0Z!5rI)fHd5Ge3roK!A*gW>|O2VURKx6lTH0t`))S~mw9gtdDVSh}jv^HB&E zhDKfw8te&{M1S3f?W+&4h`atW0Hsifm52b~7bN(^_ zcTPQa@4?(%U-r5DAA`|r`(Hph&!FvPtR#?d36x^`2Vfa-6FTyVy;D>pV;j2Vh zH+?vQM4ZdEr57=c@2p63C(KH2P184;M$1Q%N4f1AIv?D;eDat33*#Q$^klQPqsB=j@t2le0pTmq^P9W`ZM0yc}eTAs@x`N?y(kg?@v89 zs?wg>)16Q-v$t#MkGec5FY?cg*{__pe%d*=WrTTr^YiuD*MA|7HBRVRy?i(N_UcQQ z_bq#G_YXS{b{?WGb;Rvh<^QB@eKb-*MxMRmY3n+%aDH>tytghM7#crx=VUSGo0GeA z-|oIqlT|D1-#9zv2KvX|NcwUlI^<{OzB&$(To+@m*Q^!xk6S}Lu&?Iz)4zS3iu~R& zBC4<;;{KHpV})z&CG)nlcF#FwKaq&kani3>Q<7RY9-H1(8r!x0IZ#TiEw|;)Xe!M( zrPhrcQ;=2L+AuPa8Ij+9ZdqW$(MQGx*txCSQs#V@c}hi^jy{H@G*!o+%|XU(IsKLU zrFe1a{H-}pr`CbenOA#SV&07C$cw4%N$oy^F2Y}aan`mswlDU!(NjMxK76r4mig??bH%wcSIOZT0_w|s^mL;@A$L<|df4n!M zxj1S2wdmSb*=%3586gg}C2m-<=n_OJ*GG0{uI;1g^A`UDbnOYi literal 0 HcmV?d00001 diff --git a/src/main/resources/data/extendedae_plus/recipe/infinity_biginteger_cell.json b/src/main/resources/data/extendedae_plus/recipe/infinity_biginteger_cell.json new file mode 100644 index 0000000..1949a53 --- /dev/null +++ b/src/main/resources/data/extendedae_plus/recipe/infinity_biginteger_cell.json @@ -0,0 +1,18 @@ +{ + "type": "minecraft:crafting_shapeless", + "ingredients": [ + { "item": "minecraft:turtle_helmet" }, + { "item": "minecraft:dragon_head" }, + { "item": "minecraft:totem_of_undying" }, + { "item": "minecraft:echo_shard" }, + { "item": "ae2:cell_component_256k" }, + { "item": "minecraft:heart_of_the_sea" }, + { "item": "minecraft:nether_star" }, + { "item": "minecraft:netherite_block" }, + { "item": "minecraft:enchanted_golden_apple" } + ], + "result": { + "id": "extendedae_plus:infinity_biginteger_cell", + "count": 1 + } +} \ No newline at end of file