diff --git a/README.md b/README.md
index d8d6011..ca05466 100644
--- a/README.md
+++ b/README.md
@@ -42,5 +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
请查阅各上游项目以获取完整与最新的许可证信息。第三方组件的许可证与版权归其各自作者所有。
diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
index 5bc94d1..6a84267 100644
--- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
+++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
@@ -1,14 +1,19 @@
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.client.ClientRegistrar;
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.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@@ -46,6 +51,7 @@ public class ExtendedAEPlus {
// 注册到Forge事件总线
MinecraftForge.EVENT_BUS.register(this);
+ MinecraftForge.EVENT_BUS.addListener(ExtendedAEPlus::onLevelLoad);
// 注册通用配置
ModConfig.init();
// ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC);
@@ -55,6 +61,8 @@ public class ExtendedAEPlus {
* 通用初始化设置
*/
private void commonSetup(final FMLCommonSetupEvent event) {
+ StorageCells.addCellHandler(InfinityBigIntegerCellHandler.INSTANCE);
+
// 注册本模组网络通道与数据包
event.enqueueWork(() -> {
// 注册升级卡
@@ -104,4 +112,11 @@ public class ExtendedAEPlus {
} catch (Exception ignored) {}
}
}
+
+ // 在世界加载时注册/加载 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..19273c4
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java
@@ -0,0 +1,375 @@
+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.nbt.CompoundTag;
+import net.minecraft.nbt.ListTag;
+import net.minecraft.network.chat.Component;
+import net.minecraft.world.item.ItemStack;
+import net.minecraftforge.common.MinecraftForge;
+import net.minecraftforge.event.TickEvent;
+
+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("#.##");
+
+ static {
+ // 在类加载时注册服务器 tick 监听器,用于在主线程合并写入
+ try {
+ MinecraftForge.EVENT_BUS.addListener(InfinityBigIntegerCellInventory::onServerTick);
+ } catch (Throwable ignored) {
+ // 保守降级:若注册失败,不阻塞实例化
+ }
+ }
+
+ // 关联的 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 回调:合并并执行待持久化项
+ private 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) {
+ // 忽略单项错误,继续处理其余队列
+ }
+ }
+ }
+
+ // 将 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() {
+ return stack.hasTag() && stack.getOrCreateTag().contains("uuid");
+ }
+
+ // 获取物品堆栈的UUID
+ public UUID getUUID() {
+ if (this.hasUUID())
+ return stack.getOrCreateTag().getUUID("uuid");
+ else
+ return null;
+ }
+
+ // 获取或初始化存储映射
+ private Object2ObjectMap getCellStoredMap() {
+ if (storedMap == null) {
+ storedMap = new Object2ObjectOpenHashMap<>();
+ this.loadCellStoredMap();
+ }
+ return storedMap;
+ }
+
+ // 从存储中加载物品映射
+ private void loadCellStoredMap() {
+ boolean corruptedTag = false; // 标记数据是否损坏
+ if (!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++) {
+ 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);
+ }
+ }
+ }
+
+ // 获取所有可用的物品堆栈及其数量
+ @Override
+ public void getAvailableStacks(KeyCounter out) {
+ BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE);
+ Object2ObjectMap map = getCellStoredMap();
+ for (Object2ObjectMap.Entry entry : map.object2ObjectEntrySet()) {
+ BigInteger v = entry.getValue();
+ long toAdd = v.compareTo(maxLong) > 0 ? Long.MAX_VALUE : v.longValue();
+ out.add(entry.getKey(), toAdd);
+ }
+ }
+
+ // 持久化存储单元数据到全局存储
+ @Override
+ public void persist() {
+ if (this.isPersisted)
+ return;
+ Object2ObjectMap map = this.getCellStoredMap();
+ if (map.isEmpty()) {
+ // 如果存储为空,移除UUID和全局存储中的数据
+ if (this.hasUUID()) {
+ getStorageInstance().removeCell(getUUID());
+ if (stack.getTag() != null) {
+ stack.getTag().remove("uuid");
+ // 移除缓存的 total 字段
+ stack.getTag().remove("total");
+ }
+ }
+ 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());
+ 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 的 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());
+ }
+ }
+ 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()) {
+ stack.getOrCreateTag().putUUID("uuid", UUID.randomUUID());
+ 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..5b550a1
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java
@@ -0,0 +1,68 @@
+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;
+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.level.Level;
+import org.jetbrains.annotations.NotNull;
+
+import javax.annotation.Nullable;
+import java.math.BigInteger;
+import java.util.List;
+
+public class InfinityBigIntegerCellItem extends Item {
+
+ 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,
+ @NotNull List tooltip,
+ @NotNull TooltipFlag context) {
+ Preconditions.checkArgument(stack.getItem() == this);
+ // 仅在 ItemStack 自身存在 UUID 时显示 UUID,避免触发持久化或加载逻辑
+ 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))
+ );
+ // 读取并显示已缓存的 total(支持 long 或 string),使用格式化函数展示友好单位
+ 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) {
+ // 解析失败保持为 0
+ }
+ String formatted = InfinityBigIntegerCellInventory.formatBigInteger(total);
+ tooltip.add(
+ Component.literal("Byte: ").withStyle(ChatFormatting.GRAY).append(Component.literal(formatted).withStyle(ChatFormatting.AQUA))
+ );
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
index 68bdba4..dc4a03c 100644
--- a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
+++ b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
@@ -33,6 +33,8 @@ public final class ModCreativeTabs {
output.accept(ModItems.createEntitySpeedCardStack(4));
output.accept(ModItems.createEntitySpeedCardStack(8));
output.accept(ModItems.createEntitySpeedCardStack(16));
+
+ output.accept(ModItems.INFINITY_BIGINT_ITEM_CELL.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 7341c99..fc6a2e5 100644
--- a/src/main/java/com/extendedae_plus/init/ModItems.java
+++ b/src/main/java/com/extendedae_plus/init/ModItems.java
@@ -6,6 +6,7 @@ import appeng.items.parts.PartModelsHelper;
import com.extendedae_plus.ExtendedAEPlus;
import com.extendedae_plus.ae.definitions.upgrades.EntitySpeedCardItem;
import com.extendedae_plus.ae.items.EntitySpeedTickerPartItem;
+import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
import net.minecraftforge.registries.DeferredRegister;
@@ -65,6 +66,11 @@ public final class ModItems {
() -> new EntitySpeedCardItem(new Item.Properties())
);
+ public static final RegistryObject- INFINITY_BIGINT_ITEM_CELL = ITEMS.register(
+ "infinity_item_cell", InfinityBigIntegerCellItem::new
+ );
+
+
/**
* 为 PartItem 注册 AE2 部件模型。
* 在客户端进行模型/几何体注册时调用。
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..e24a5ab
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java
@@ -0,0 +1,114 @@
+package com.extendedae_plus.util.storage;
+
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.ListTag;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.level.saveddata.SavedData;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * InfinityStorageManager
+ *
+ * 世界级别的持久化容器,集中管理所有 InfinityBigInteger 存储单元的序列化数据。
+ * 功能要点:
+ * - 在世界加载时从存档恢复所有 cell 的数据
+ * - 提供按 UUID 获取/创建单个 cell 的数据容器
+ * - 在世界保存时将内存数据打包为 NBT 写回存档
+ */
+public class InfinityStorageManager extends SavedData {
+
+ /**
+ * 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<>();
+
+ public InfinityStorageManager() {
+ setDirty();
+ }
+
+ /**
+ * 从 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")));
+ }
+ 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();
+ }
+
+ /**
+ * 获取或创建某个 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();
+ }
+}