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 倍",