From cc9dd480b43a4b203f6580957aaecb25b9cefd96 Mon Sep 17 00:00:00 2001
From: C-H716 <1536152356@qq.com>
Date: Fri, 26 Sep 2025 15:52:51 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E7=A7=BB=E6=A4=8D=E6=97=A0=E9=99=90?=
=?UTF-8?q?=E7=9B=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../com/extendedae_plus/ExtendedAEPlus.java | 34 +-
.../InfinityBigIntegerCellHandler.java | 22 +
.../InfinityBigIntegerCellInventory.java | 414 ++++++++++++++++++
.../ae/items/InfinityBigIntegerCellItem.java | 88 ++++
.../extendedae_plus/init/ModCreativeTabs.java | 2 +
.../com/extendedae_plus/init/ModItems.java | 6 +
.../util/storage/InfinityConstants.java | 30 ++
.../util/storage/InfinityDataStorage.java | 48 ++
.../util/storage/InfinityStorageManager.java | 151 +++++++
.../assets/extendedae_plus/lang/en_us.json | 3 +
.../assets/extendedae_plus/lang/zh_cn.json | 3 +
11 files changed, 799 insertions(+), 2 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/InfinityConstants.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
diff --git a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
index 5a01c42..43285e6 100644
--- a/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
+++ b/src/main/java/com/extendedae_plus/ExtendedAEPlus.java
@@ -2,14 +2,18 @@ package com.extendedae_plus;
import appeng.api.parts.IPart;
import appeng.api.parts.PartModels;
+import appeng.api.storage.StorageCells;
import appeng.block.AEBaseEntityBlock;
import appeng.blockentity.crafting.CraftingBlockEntity;
import appeng.items.parts.PartModelsHelper;
+import com.extendedae_plus.ae.api.storage.InfinityBigIntegerCellHandler;
import com.extendedae_plus.config.ModConfigs;
import com.extendedae_plus.init.*;
+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.MinecraftServer;
import net.minecraft.world.level.block.Blocks;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.bus.api.SubscribeEvent;
@@ -18,7 +22,10 @@ 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.server.ServerStartedEvent;
import net.neoforged.neoforge.event.server.ServerStartingEvent;
+import net.neoforged.neoforge.event.server.ServerStoppedEvent;
+import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
// The value here should match an entry in the META-INF/neoforge.mods.toml file
@@ -52,7 +59,8 @@ 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::onServerStarted);
+ NeoForge.EVENT_BUS.addListener(ExtendedAEPlus::onServerStopped);
// 注册配置:接入自定义的 ModConfigs
modContainer.registerConfig(ModConfig.Type.COMMON, ModConfigs.COMMON_SPEC, "extendedae_plus-common.toml");
modContainer.registerConfig(ModConfig.Type.CLIENT, ModConfigs.CLIENT_SPEC, "extendedae_plus-client.toml");
@@ -69,6 +77,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(() -> {
@@ -116,7 +125,28 @@ public class ExtendedAEPlus {
});
}
- // 移除示例 addCreative 事件,避免将示例物品加入原版标签
+ @Nullable
+ private static InfinityStorageManager storageManager;
+
+ @Nullable
+ private static MinecraftServer storageManagerServer;
+
+ private static void onServerStarted(ServerStartedEvent event) {
+ storageManagerServer = event.getServer();
+ storageManager = InfinityStorageManager.getInstance(event.getServer());
+ }
+
+ private static void onServerStopped(ServerStoppedEvent event) {
+ if (storageManagerServer == event.getServer()) {
+ storageManagerServer = null;
+ storageManager = null;
+ }
+ }
+
+ @Nullable
+ public static InfinityStorageManager currentStorageManager() {
+ return storageManager;
+ }
// You can use SubscribeEvent and let the Event Bus discover methods to call
@SubscribeEvent
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..3b7aa46
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellHandler.java
@@ -0,0 +1,22 @@
+package com.extendedae_plus.ae.api.storage;
+
+import appeng.api.storage.cells.ICellHandler;
+import appeng.api.storage.cells.ISaveProvider;
+import com.extendedae_plus.ExtendedAEPlus;
+import com.extendedae_plus.ae.items.InfinityBigIntegerCellItem;
+import net.minecraft.world.item.ItemStack;
+
+public class InfinityBigIntegerCellHandler implements ICellHandler {
+
+ public static final InfinityBigIntegerCellHandler INSTANCE = new InfinityBigIntegerCellHandler();
+
+ @Override
+ public boolean isCell(ItemStack is) {
+ return is.getItem() instanceof InfinityBigIntegerCellItem;
+ }
+
+ @Override
+ public InfinityBigIntegerCellInventory getCellInventory(ItemStack is, ISaveProvider container) {
+ return InfinityBigIntegerCellInventory.createInventory(is, container, ExtendedAEPlus.currentStorageManager());
+ }
+}
\ 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..675c41e
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/ae/api/storage/InfinityBigIntegerCellInventory.java
@@ -0,0 +1,414 @@
+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 appeng.core.AELog;
+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;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
+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 org.jetbrains.annotations.Nullable;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * 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 {
+ private final InfinityBigIntegerCellItem cell;
+ // 磁盘本身
+ private final ItemStack self;
+ @Nullable
+ private final InfinityStorageManager storageManager;
+ // AE2 提供的保存提供者,用于在容器中批量保存时触发回调
+ private final ISaveProvider container;
+ // 存储物品键和数量的映射
+ private Object2ObjectMap AEKey2AmountsMap;
+ // 存储的物品种类数量
+ private int totalAEKeyType;
+ // 存储的物品总数
+ private BigInteger totalAEKey2Amounts = BigInteger.ZERO;
+ // 标记是否已持久化到 SavedData
+ private boolean isPersisted = true;
+
+
+ public InfinityBigIntegerCellInventory(InfinityBigIntegerCellItem cell,
+ ItemStack stack,
+ ISaveProvider saveProvider,
+ @Nullable InfinityStorageManager storageManager) {
+ // 保存存储单元类型(InfinityBigIntegerCellItem 实例),用于访问磁盘属性
+ this.cell = cell;
+ // 保存物品堆栈,表示磁盘本身,包含运行时的 NBT 数据
+ this.self = stack;
+ // 保存提供者,用于触发数据保存
+ this.container = saveProvider;
+ // 初始化 storedAmounts 为 null,延迟加载物品数据
+ this.AEKey2AmountsMap = null;
+ this.storageManager = storageManager;
+ // 初始化磁盘数据
+ initData();
+ }
+
+ // 将 BigInteger 格式化为带单位的字符串,保留两位小数
+ public static String formatBigInteger(BigInteger number) {
+ // 使用方法局部的 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"};
+ 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];
+ }
+
+ // 静态方法,创建存储单元库存
+ public static InfinityBigIntegerCellInventory createInventory(ItemStack stack,
+ ISaveProvider saveProvider,
+ @Nullable InfinityStorageManager storageManager) {
+ // 检查物品堆栈是否为空
+ 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, storageManager);
+ }
+
+ // 获取磁盘的 InfinityDataStorage 数据
+ private InfinityDataStorage getCellStorage() {
+ // 如果磁盘有 UUID,返回对应的 InfinityDataStorage
+ if (getUUID() != null && this.storageManager != null) {
+ return storageManager.getOrCreateCell(getUUID());
+ } else {
+ // 否则返回空的 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.getTotalAEKey2Amounts().equals(BigInteger.ZERO)) {
+ return CellState.EMPTY;
+ }
+ // 否则返回满状态
+ return CellState.NOT_EMPTY;
+ }
+
+ // 获取存储单元的待机能耗
+ @Override
+ public double getIdleDrain() {
+ return 512;
+ }
+
+ // 持久化存储单元数据到全局存储
+ @Override
+ public void persist() {
+ if (this.isPersisted || this.storageManager == null)
+ return;
+
+ if (this.totalAEKey2Amounts.equals(BigInteger.ZERO)) {
+ if (hasUUID()) {
+ this.storageManager.removeCell(getUUID());
+ // 从 DataComponents.CUSTOM_DATA 里移除对应字段
+ CustomData data = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
+ CompoundTag tag = data.copyTag();
+ tag.remove(InfinityConstants.INFINITY_CELL_UUID);
+ tag.remove(InfinityConstants.INFINITY_ITEM_TOTAL);
+ tag.remove(InfinityConstants.INFINITY_ITEM_TYPES);
+ // backward compat
+ tag.remove(InfinityConstants.INFINITY_CELL_ITEM_COUNT);
+
+ self.set(DataComponents.CUSTOM_DATA, CustomData.of(tag));
+
+ 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(this.storageManager.getRegistries()));
+ CompoundTag amountTag = new CompoundTag();
+ amountTag.putByteArray("value", amount.toByteArray());
+ amounts.add(amountTag);
+
+ itemCount = itemCount.add(amount);
+ }
+ }
+
+ if (keys.isEmpty()) {
+ this.storageManager.updateCell(getUUID(), new InfinityDataStorage());
+ } else {
+ this.storageManager.modifyDisk(getUUID(), keys, amounts, itemCount);
+ }
+
+ // 更新存储的物品种类数量
+ this.totalAEKeyType = this.AEKey2AmountsMap.size();
+ // 更新存储的物品总数
+ this.totalAEKey2Amounts = itemCount;
+ // 将物品总数与种类数量存入物品堆栈的 NBT(用于快捷查看/tooltip),同时保留旧字段以兼容历史版本
+
+ // 写回 DataComponents.CUSTOM_DATA(替代 getOrCreateTag)
+ CompoundTag tag = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).copyTag();
+ tag.putByteArray(InfinityConstants.INFINITY_ITEM_TOTAL, itemCount.toByteArray());
+ tag.putInt(InfinityConstants.INFINITY_ITEM_TYPES, this.totalAEKeyType);
+ tag.putByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT, itemCount.toByteArray());
+ self.set(DataComponents.CUSTOM_DATA, CustomData.of(tag));
+ // 标记数据已持久化
+ this.isPersisted = true;
+ }
+
+ // 获取存储单元的描述(此处返回null,可自定义)
+ @Override
+ public Component getDescription() {
+ return null;
+ }
+
+ // 获取存储的物品总数
+ public BigInteger getTotalAEKey2Amounts() {
+ return this.totalAEKey2Amounts;
+ }
+
+ // 获取存储的物品种类数量
+ public int getTotalAEKeyType() {
+ return this.totalAEKeyType;
+ }
+
+ // 判断物品堆栈是否有UUID
+ public boolean hasUUID() {
+ CustomData data = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
+ return !data.isEmpty() && data.copyTag().contains(InfinityConstants.INFINITY_CELL_UUID);
+ }
+
+ // 获取物品堆栈的UUID
+ public UUID getUUID() {
+ CustomData data = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
+ if (!data.isEmpty() && data.copyTag().contains(InfinityConstants.INFINITY_CELL_UUID)) {
+ return data.copyTag().getUUID(InfinityConstants.INFINITY_CELL_UUID);
+ }
+ return null;
+ }
+
+ // 获取或初始化存储映射
+ private Object2ObjectMap getCellStoredMap() {
+ if (AEKey2AmountsMap == null) {
+ AEKey2AmountsMap = new Object2ObjectOpenHashMap<>();
+ this.loadCellStoredMap();
+ }
+ return AEKey2AmountsMap;
+ }
+
+ // 获取所有可用的物品堆栈及其数量
+ @Override
+ public void getAvailableStacks(KeyCounter out) {
+ BigInteger maxLong = BigInteger.valueOf(Long.MAX_VALUE);
+ if (this.getCellStoredMap() == null) return;
+ for (var entry : this.getCellStoredMap().object2ObjectEntrySet()) {
+ AEKey key = entry.getKey();
+ BigInteger value = entry.getValue();
+
+ // 获取 KeyCounter 中已有的值
+ long existing = out.get(key);
+
+ // 计算总和并限制到 Long.MAX_VALUE
+ BigInteger sum = BigInteger.valueOf(existing).add(value);
+ long toSet = sum.compareTo(maxLong) > 0 ? Long.MAX_VALUE : sum.longValue();
+ // 更新 KeyCounter
+ if (existing == Long.MAX_VALUE) {
+ continue;
+ }
+ long delta = toSet - existing;
+ if (delta != 0) {
+ out.add(key, delta);
+ }
+ }
+ }
+
+
+ // 从存储中加载物品映射
+ private void loadCellStoredMap() {
+ if (this.storageManager == null) {
+ return;
+ }
+
+ boolean dataCorruption = false;
+ if (self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).isEmpty()) 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());
+ }
+
+ var registries = this.storageManager.getRegistries();
+
+ // 遍历数量和键,加载到 AEKey2AmountsMap
+ for (int i = 0; i < amounts.size(); i++) {
+ AEKey key = AEKey.fromTagGeneric(registries,keys.getCompound(i));
+ BigInteger amount = new BigInteger(amounts.getCompound(i).getByteArray("value"));
+ // 检查数据是否损坏
+ if (amount.compareTo(BigInteger.ZERO) <= 0 || key == null) {
+ dataCorruption = true;
+ } else {
+ AEKey2AmountsMap.put(key, amount);
+ }
+ }
+ if (dataCorruption) {
+ this.saveChanges();
+ }
+ }
+
+ // 标记数据需要保存,并通知容器或直接持久化
+ 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) {
+ return 0;
+ }
+ // 不允许存储无限单元自身
+ if (what instanceof AEItemKey itemKey && itemKey.getItem() instanceof InfinityBigIntegerCellItem) {
+ return 0;
+ }
+
+ // 如果没有 UUID,且服务器端存储管理器已就绪,则生成 UUID 并初始化存储
+ if (storageManager != null && !this.hasUUID()) {
+ // 取出自定义 NBT(如果没有就返回空)
+ CustomData data = self.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
+ CompoundTag tag = data.copyTag();
+
+ // 生成新的 UUID 并写入
+ UUID newUUID = UUID.randomUUID();
+ tag.putUUID(InfinityConstants.INFINITY_CELL_UUID, newUUID);
+
+ // 回写到 ItemStack
+ self.set(DataComponents.CUSTOM_DATA, CustomData.of(tag));
+
+ // 初始化存储
+ this.storageManager.getOrCreateCell(newUUID);
+
+ // 加载已存储的映射
+ loadCellStoredMap();
+ }
+ // 获取当前物品数量
+ BigInteger currentAmount = this.getCellStoredMap().getOrDefault(what, BigInteger.ZERO);
+
+ if (mode == Actionable.MODULATE) {
+ // 实际插入,更新数量并保存
+ BigInteger newAmount = currentAmount.add(BigInteger.valueOf(amount));
+ getCellStoredMap().put(what, newAmount);
+ this.saveChanges();
+ }
+ return amount;
+ }
+
+ // 从存储单元提取物品
+ @Override
+ public long extract(AEKey what, long amount, Actionable mode, IActionSource source) {
+ BigInteger currentAmount = this.getCellStoredMap().getOrDefault(what, BigInteger.ZERO);
+ // 如果有物品可提取
+ if (currentAmount.compareTo(BigInteger.ZERO) > 0) {
+
+ BigInteger requested = BigInteger.valueOf(amount);
+
+ // 如果提取数量大于等于当前数量
+ if (requested.compareTo(currentAmount) >= 0) {
+ if (mode == Actionable.MODULATE) {
+ getCellStoredMap().remove(what);
+ this.saveChanges();
+ }
+ return currentAmount.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0 ? Long.MAX_VALUE : currentAmount.longValue();
+ } else {
+ // 提取部分数量
+ if (mode == Actionable.MODULATE) {
+ getCellStoredMap().put(what, currentAmount.subtract(requested));
+ this.saveChanges();
+ }
+ return requested.longValue();
+ }
+ }
+ return 0;
+ }
+
+ // 获取存储单元内所有物品的总数量(格式化字符串)
+ public String getTotalStorage() {
+ // 使用缓存的 totalStored,避免每次全表扫描
+ 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
new file mode 100644
index 0000000..2ae9fb0
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/ae/items/InfinityBigIntegerCellItem.java
@@ -0,0 +1,88 @@
+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.core.component.DataComponents;
+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.item.component.CustomData;
+
+import java.math.BigInteger;
+import java.util.List;
+
+public class InfinityBigIntegerCellItem extends Item implements ICellWorkbenchItem {
+
+ public InfinityBigIntegerCellItem() {
+ super(new Properties().stacksTo(1).fireResistant());
+ }
+
+ @Override
+ public void appendHoverText(ItemStack stack, TooltipContext context, List tooltip, 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);
+ // 仅在 ItemStack 自身存在 UUID 时显示 UUID,避免触发持久化或加载逻辑
+ CustomData customData = stack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
+
+ if (!customData.isEmpty()) {
+ CompoundTag tag = customData.copyTag();
+
+ if (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))
+ );
+ }
+
+ if (tag.contains(InfinityConstants.INFINITY_ITEM_TYPES)) {
+ try {
+ 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))
+ );
+ } catch (Exception ignored) {}
+ }
+
+ if (tag.contains(InfinityConstants.INFINITY_ITEM_TOTAL)) {
+ try {
+ byte[] bytes = tag.getByteArray(InfinityConstants.INFINITY_ITEM_TOTAL);
+ BigInteger total = new 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) {}
+ } else if (tag.contains(InfinityConstants.INFINITY_CELL_ITEM_COUNT)) {
+ try {
+ byte[] bytes = tag.getByteArray(InfinityConstants.INFINITY_CELL_ITEM_COUNT);
+ BigInteger total = new 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) {}
+ }
+ }
+ }
+
+ @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/init/ModCreativeTabs.java b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
index 10d9144..d5fc5da 100644
--- a/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
+++ b/src/main/java/com/extendedae_plus/init/ModCreativeTabs.java
@@ -29,6 +29,8 @@ public final class ModCreativeTabs {
output.accept(ModItems.createEntitySpeedCardStack((byte) 4));
output.accept(ModItems.createEntitySpeedCardStack((byte) 8));
output.accept(ModItems.createEntitySpeedCardStack((byte) 16));
+
+ 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 e985c1e..c059433 100644
--- a/src/main/java/com/extendedae_plus/init/ModItems.java
+++ b/src/main/java/com/extendedae_plus/init/ModItems.java
@@ -3,6 +3,7 @@ package com.extendedae_plus.init;
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.minecraft.world.item.ItemStack;
@@ -62,6 +63,11 @@ public final class ModItems {
() -> new EntitySpeedCardItem(new Item.Properties())
);
+ public static final DeferredItem- INFINITY_BIGINTEGER_CELL_ITEM = ITEMS.register(
+ "infinity_biginteger_cell", InfinityBigIntegerCellItem::new
+ );
+
+
/**
* 工厂:创建带 multiplier 的实体加速卡 ItemStack(2/4/8/16)
*/
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
new file mode 100644
index 0000000..6462128
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/util/storage/InfinityDataStorage.java
@@ -0,0 +1,48 @@
+package com.extendedae_plus.util.storage;
+
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.nbt.ListTag;
+
+import java.math.BigInteger;
+
+/**
+ * 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 ListTag keys;
+ public ListTag amounts;
+ // 存储磁盘中物品的总数,使用 BigInteger 支持大容量
+ public BigInteger itemCount;
+
+ public InfinityDataStorage() {
+ this(new ListTag(), new ListTag(), BigInteger.ZERO);
+ }
+
+ private InfinityDataStorage(ListTag keys, ListTag amounts, BigInteger itemCount) {
+ this.keys = keys;
+ this.amounts = amounts;
+ this.itemCount = itemCount;
+ }
+
+ // 将 DataStorage 数据序列化为 NBT 格式
+ public CompoundTag serializeNBT() {
+ CompoundTag nbt = new CompoundTag();
+ 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 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
new file mode 100644
index 0000000..fabcfff
--- /dev/null
+++ b/src/main/java/com/extendedae_plus/util/storage/InfinityStorageManager.java
@@ -0,0 +1,151 @@
+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 javax.annotation.Nullable;
+import java.lang.ref.WeakReference;
+import java.math.BigInteger;
+import java.util.*;
+
+/**
+ * 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 Factory FACTORY = new Factory<>(InfinityStorageManager::new, InfinityStorageManager::readNbt);
+ // 存储所有磁盘的Map,键为UUID,值为DataStorage对象
+ private final Map cells;
+ @Nullable
+ private WeakReference registries;
+
+
+ // 构造方法,初始化磁盘Map
+ public InfinityStorageManager() {
+ cells = new HashMap<>();
+ // 标记数据为“脏”,确保新创建的实例在下次保存时写入磁盘
+ this.setDirty();
+ }
+
+ // 私有构造方法,用于从已有Map创建StorageManager
+ private InfinityStorageManager(Map cells) {
+ // 确保使用已加载的数据
+ this.cells = cells;
+ // 标记数据为“脏”,确保新创建的实例在下次保存时写入磁盘
+ this.setDirty();
+ }
+
+
+ // 静态方法,从 NBT 数据反序列化创建 StorageManager 实例
+ public static InfinityStorageManager readNbt(CompoundTag nbt, HolderLookup.Provider registries) {
+ // 读取格式版本,缺省视为 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);
+ }
+
+
+ @Override
+ public CompoundTag save(CompoundTag nbt, HolderLookup.Provider provider) {
+ // 将内存中的所有 cell 序列化为一个 ListTag
+ ListTag cellList = new ListTag();
+ for (Map.Entry entry : cells.entrySet()) {
+ CompoundTag cell = new CompoundTag();
+ cell.putUUID(InfinityConstants.INFINITY_CELL_UUID, entry.getKey());
+ cell.put(InfinityConstants.INFINITY_CELL_DATA, entry.getValue().serializeNBT());
+ cellList.add(cell);
+ }
+ nbt.put(InfinityConstants.INFINITY_CELL_LIST, cellList);
+ // 写入当前格式版本号,便于未来迁移与兼容判断
+ nbt.putInt(InfinityConstants.FORMAT_VERSION_FIELD, InfinityConstants.FORMAT_VERSION);
+ return nbt;
+ }
+
+ // 返回当前已加载的所有 UUID 的不可变视图,用于命令或调试用途
+ public Set getAllLoadedUUIDs() {
+ return Collections.unmodifiableSet(cells.keySet());
+ }
+
+ // 更新或添加某个 UUID 对应的数据并标记为脏(需要保存)
+ public void updateCell(UUID uuid, InfinityDataStorage infinityDataStorage) {
+ cells.put(uuid, infinityDataStorage);
+ // 标记数据为“脏”,确保修改后的数据会在下次保存时写入磁盘
+ setDirty();
+ }
+
+ // 删除某个 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 的磁盘数据,包括堆栈键、数量和总项目数
+ 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;
+ }
+ // 更新 DataStorage 的 itemCount 字段
+ cellToModify.itemCount = itemCount;
+ // 将修改后的 DataStorage 对象更新到 cells 映射
+ updateCell(uuid, cellToModify);
+ }
+
+
+ public static InfinityStorageManager getInstance(MinecraftServer server) {
+ ServerLevel world = server.getLevel(ServerLevel.OVERWORLD);
+ var manager = world.getDataStorage().computeIfAbsent(FACTORY, InfinityConstants.SAVE_FILE_NAME);
+ manager.registries = new WeakReference<>(server.registryAccess());
+ return manager;
+ }
+
+ 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;
+ }
+}
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 babef8e..8775525 100644
--- a/src/main/resources/assets/extendedae_plus/lang/en_us.json
+++ b/src/main/resources/assets/extendedae_plus/lang/en_us.json
@@ -22,6 +22,9 @@
"item.extendedae_plus.entity_speed_card.x4": "Entity Speed Card (x4)",
"item.extendedae_plus.entity_speed_card.x8": "Entity Speed Card (x8)",
"item.extendedae_plus.entity_speed_card.x16": "Entity Speed Card (x16)",
+ "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",
"tooltip.extendedae_plus.entity_speed_card.multiplier": "Multiplier: %s",
"tooltip.extendedae_plus.entity_speed_card.max": "Maximum Effect: %s Times",
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 8592356..fe2d4a8 100644
--- a/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
+++ b/src/main/resources/assets/extendedae_plus/lang/zh_cn.json
@@ -22,6 +22,9 @@
"item.extendedae_plus.entity_speed_card.x4": "实体加速卡 (x4)",
"item.extendedae_plus.entity_speed_card.x8": "实体加速卡 (x8)",
"item.extendedae_plus.entity_speed_card.x16": "实体加速卡 (x16)",
+ "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寰",
"tooltip.extendedae_plus.entity_speed_card.multiplier": "乘数: %s",
"tooltip.extendedae_plus.entity_speed_card.max": "最大生效: %s 倍",